Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 10 additions & 5 deletions packages/project/src/objects/pm_project.hook.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,16 @@ import type { Hook, HookContext } from '@objectstack/spec/data';
* and group on a single signal.
*
* NOTE — this hook only mutates the incoming `input` payload; it never issues a
* nested `ctx.api` write. Hook bodies share one QuickJS (asyncify) sandbox that
* permits a single suspended async call, so a nested engine write crashes the
* process. That is why `progress_percent` / `actual_cost` (which would need a
* cross-object rollup from milestones / timesheets) are stored fields kept in
* sync by seed/client, not by this hook.
* nested `ctx.api` write. Nested cross-object writes from a hook are safe now
* (framework#1867 is fixed), but `progress_percent` / `actual_cost` are still
* deliberately NOT hook roll-ups — neither is a plain aggregate of a child field:
* - `actual_cost` is an externally-computed actual (hours × a person/role rate).
* `pm_timesheet` records hours only; there is no child cost column to sum, and
* the per-rate data lives outside the 4-object CHARTER schema.
* - `progress_percent` is a curated delivery metric, not a naive completed/total
* milestone count (the seeded per-project values differ where a count would
* flatten them to the same number).
* Both are kept by seed/client; see each field's note in pm_project.object.ts.
*
* IMPORTANT — the handler runs **body-only** in the QuickJS sandbox: only the
* function body ships, so module-scope helpers are NOT in scope at runtime.
Expand Down
13 changes: 9 additions & 4 deletions packages/project/src/objects/pm_project.object.ts
Original file line number Diff line number Diff line change
Expand Up @@ -181,8 +181,11 @@ export const Project = ObjectSchema.create({
required: false,
group: 'budget',
description:
'Cost to date. Maintained by seed/client or an out-of-sandbox worker — NOT a live ' +
'hook rollup (a nested write from a timesheet hook crashes the QuickJS sandbox).',
'Cost to date — an externally-computed actual (hours × a person/role rate). ' +
'pm_timesheet records hours only, so there is no child cost column to sum: this is ' +
'not a summary/hook roll-up but a value set by seed/client or a finance worker. ' +
'(Nested hook writes are safe now — framework#1867 — the blocker is the missing rate, ' +
'not the sandbox.)',
}),

// Team
Expand All @@ -206,8 +209,10 @@ export const Project = ObjectSchema.create({
min: 0,
max: 100,
description:
'Share of completed milestones. Maintained by seed/client (not a live hook ' +
'rollup — see actual_cost note).',
'Delivery progress (0–100). A curated metric, not a naive completed/total milestone ' +
'count — the seeded per-project values differ where a count would flatten them — so it ' +
'is kept by seed/client. It could be made a live milestone roll-up via an afterInsert/' +
'afterUpdate hook now that framework#1867 is fixed, if count-based progress is acceptable.',
}),
},

Expand Down
3 changes: 2 additions & 1 deletion packages/project/src/views/pm_timesheet.view.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@ import { defineView } from '@objectstack/spec/ui';

/**
* Timesheet views — daily effort entries, grouped by project, with a billable
* tab. Timesheets feed the project's actual_cost (maintained out of sandbox).
* tab. Timesheets record effort (hours); the project's actual_cost is computed
* externally (hours × rate), not rolled up from these rows.
*/
export const TimesheetViews = defineView({
list: {
Expand Down
Loading