diff --git a/packages/project/src/objects/pm_project.hook.ts b/packages/project/src/objects/pm_project.hook.ts index 79d8d24..1ad6b00 100644 --- a/packages/project/src/objects/pm_project.hook.ts +++ b/packages/project/src/objects/pm_project.hook.ts @@ -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. diff --git a/packages/project/src/objects/pm_project.object.ts b/packages/project/src/objects/pm_project.object.ts index bc1cdae..2dac9a7 100644 --- a/packages/project/src/objects/pm_project.object.ts +++ b/packages/project/src/objects/pm_project.object.ts @@ -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 @@ -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.', }), }, diff --git a/packages/project/src/views/pm_timesheet.view.ts b/packages/project/src/views/pm_timesheet.view.ts index c70bf8f..6a5d4d4 100644 --- a/packages/project/src/views/pm_timesheet.view.ts +++ b/packages/project/src/views/pm_timesheet.view.ts @@ -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: {