Skip to content

Commit 2829877

Browse files
committed
fix(example-showcase): functions 用裸函数形态注册 sweepProjectHealth (#4774)
合并 main 后 `pnpm build` 红:#4925 之外的既有缺陷被这次改动踩到。 `{ handler, effect: 'writes' }` 这个声明形态无法通过 `objectstack build` —— CLI 把它降级成 `{ handler: 'sweepProjectHealth', effect: 'writes' }` (lowerCallables 自己的测试就是这么断言的),而 `FlowFunctionEntrySchema` 的三个联合分支只接受:裸 callable、handler 是 callable 的声明、裸字符串 ref,唯独没有「handler 已被降级为字符串的声明」。于是从源码加载全绿 (dev / validate / vitest),只有构建产物这条路失败——正是 #4343 为裸形态 修掉的那个不对称,晚了一个形态。已拆 #4976。 `packages/spec` 本轮零改动,所以 showcase 改用裸函数形态,并在原处留下 不要改回去的理由 + issue 链接。运行时没有损失:`effect` 只有 script 节点 的 `unmeasuredEffect` 一个消费者,job 这条路的 `collectBundleFunctions` 本来就只保留 handler。 守卫相应调整为「functions 条目必须是构建能承载的形态」,并注明 #4976 落地 后应当删除该守卫而不是绕过它。 复核(合并 main 后重跑):`pnpm build` 全仓 71/71 绿,showcase 构建产物 bundle 到 2 个 handler;typecheck / 84 tests / validate 绿;全新库冷启仍是 2 条无关告警,且 `[migration] new datastore attested at creation` 与 `[value-shape] this deployment has verified …— enforced` 均出现。 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NrmBxj8rK2uGCnh9aipjwX
1 parent 83afe73 commit 2829877

3 files changed

Lines changed: 37 additions & 14 deletions

File tree

.changeset/showcase-inert-wirings.md

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,9 @@ nothing — `@objectstack/example-showcase` is private.
1111
Sweep" had never run. Implemented for real: it recomputes
1212
`showcase_project.health` from budget burn measured against delivered task
1313
progress, over an engine handle captured at `onEnable` (a job handler is
14-
invoked with `{ jobId, data }` and no data engine), and is registered with
15-
`effect: 'writes'` so the run reports "cannot count these writes" rather than
16-
silently claiming it wrote nothing.
14+
invoked with `{ jobId, data }` and no data engine). It is registered in the
15+
bare-callable form rather than the `effect: 'writes'` declaration it wants:
16+
that declared form cannot survive `objectstack build` today, filed as #4976.
1717
- **`showcase.export_data` now materializes.** The capability declared no
1818
owning package, so it was never written to `sys_capability` — leaving
1919
`OpsPermissionSet` granting a permission that would never exist. It now
@@ -34,7 +34,8 @@ nothing — `@objectstack/example-showcase` is private.
3434
declared, with its gallery binding, and is populated by uploading a cover.
3535

3636
Guarded by `test/inert-wirings.test.ts`: every declared job's handler must
37-
resolve against `defineStack({ functions })`, every declared capability must
37+
resolve against `defineStack({ functions })` in a form the build can carry,
38+
every declared capability must
3839
resolve an owning package, no permission set may grant an undeclared
3940
capability, no **source file** may author `retryDelayMs` (the parsed stack
4041
cannot answer this — the conversion has already rewritten it), and no seeded

examples/app-showcase/objectstack.config.ts

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -214,13 +214,23 @@ export default defineStack({
214214
// `sweepProjectHealth` — the handler `HealthSweepJob` names — lives here too.
215215
// It is the case the pure contract does not cover: a nightly sweep has no
216216
// downstream declarative node to persist for it, so it writes over an engine
217-
// handle captured at `onEnable` and says so with `effect: 'writes'` (#4396).
218-
// The declaration grants nothing; it makes the run report "cannot count these
219-
// writes" instead of silently claiming it wrote nothing.
217+
// handle captured at `onEnable`.
218+
//
219+
// ⚠️ Do NOT rewrite this as `{ handler: sweepProjectHealth, effect: 'writes' }`.
220+
// That declared form (#4396) is the honest spelling for a writer and is what
221+
// this entry wants — but it cannot survive `objectstack build` today: the CLI
222+
// lowers it to `{ handler: 'sweepProjectHealth', effect: 'writes' }` and
223+
// `FlowFunctionEntrySchema` accepts a bare callable, a declaration whose
224+
// `handler` is a CALLABLE, or a bare string ref — never a declaration whose
225+
// handler has been lowered to a string. `pnpm build` fails with
226+
// `functions: invalid_union`. Filed as #4976; switch back once it lands.
227+
// Nothing is lost at runtime meanwhile: `effect` has exactly one consumer,
228+
// the `script` node's `unmeasuredEffect` metric, and the JOB path drops it
229+
// (`collectBundleFunctions` keeps only the handler).
220230
functions: {
221231
summarizeCompletedTask: ({ input }: { input: Record<string, unknown> }) =>
222232
`Completed: ${String(input.title ?? 'task')} (priority ${String(input.priority ?? 'normal')}).`,
223-
sweepProjectHealth: { handler: sweepProjectHealth, effect: 'writes' as const },
233+
sweepProjectHealth,
224234
},
225235
jobs: allJobs,
226236
emailTemplates: allEmails,

examples/app-showcase/test/inert-wirings.test.ts

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -96,12 +96,24 @@ describe('declarative jobs resolve their handler (#4774 ①)', () => {
9696
});
9797
}
9898

99-
it('the health sweep declares `effect: writes` (#4396)', () => {
100-
// A job handler that does its own data I/O must say so: an UNDECLARED
101-
// writer is counted as having written nothing, which makes the run's own
102-
// report a lie rather than an honest "cannot count".
103-
const entry = functionEntry('sweepProjectHealth') as { effect?: string } | undefined;
104-
expect(entry?.effect).toBe('writes');
99+
it('every functions entry is authored in a form `objectstack build` can carry', () => {
100+
// `objectstack build` LOWERS each inline callable to a serialisable string
101+
// ref before the stack is parsed, and `FlowFunctionEntrySchema` accepts a
102+
// bare callable, a declaration whose `handler` is a CALLABLE, or a bare
103+
// string ref — but NOT a declaration whose handler has been lowered to a
104+
// string, which is exactly what the CLI emits for the declared form
105+
// (`{ handler: fn, effect: 'writes' }`, #4396). So authoring the declared
106+
// form here builds green from source and fails `pnpm build` with
107+
// `functions: invalid_union`. Filed as #4976.
108+
//
109+
// Pinning the bare form keeps that failure out of the reference app until
110+
// the schema accepts the lowered declaration. Delete this guard — don't
111+
// work around it — when #4976 lands.
112+
const declared = functionNames().filter((name) => typeof functionEntry(name) !== 'function');
113+
expect(
114+
declared,
115+
`declared-form functions entry/entries cannot survive \`objectstack build\` (#4976): ${declared.join(', ')}`,
116+
).toEqual([]);
105117
});
106118
});
107119

0 commit comments

Comments
 (0)