What happens
On origin/main (8293e45) every hook in src/hooks/stamp.hook.ts is written against ctx.ql.find(...). objectstack build / objectstack dev lower the inline handler functions to sandboxed bodies (compile step: "Lowering inline handlers… all 6 callables are body-only"), and the QuickJS sandbox context has no ql — cross-object reads there are ctx.api.object(NAME).findOne({ where }), gated by the api.read capability (skill objectstack-data, references/data-hooks.md, "What lives on the sandbox ctx").
Result under pnpm dev: creating or updating any ats_employer_member, ats_job, ats_application, ats_interview, ats_offer or ats_candidate_credential row through REST fails with HTTP 500 and the log shows
ERROR [BodyRunner] sandboxed hook threw {"appId":"ats","hook":"ats_job_stamp","error":{"message":"hook 'ats_job_stamp' threw: TypeError: cannot read property 'find' of undefined"
[REST] Unhandled error: SandboxError: hook 'ats_job_stamp' threw: TypeError: cannot read property 'find' of undefined
(observed for ats_employer_member_stamp, ats_job_stamp, ats_application_stamp on a --fresh in-memory boot of 17.3.0; the other three hooks have the same shape). pnpm validate / lint / typecheck are all green — the mismatch is a runtime-only fault, which is why it survived card 06.
Why it matters
Nothing in the employer or transaction domain can be written through the API while this stands: no job, no application, no offer, no member. It blocks the M2 views (#2), the seed loader for #5 if it runs the hooks, and the runtime half of the M3 acceptance. The stamps are load-bearing for security (employer_org / candidate_user feed the row-level policies), so the fix is not "drop the hooks".
Verified fix shape
A body-form stand-in (body: { language: 'js', capabilities: ['api.read'], source: ... } using ctx.api.object('ats_employer').findOne({ where: { id } }), ctx.input, ctx.previous, !ctx.previous for insert) made the same inserts succeed with employer_org, candidate_user and display_name stamped as designed — measured while verifying #6 (that stand-in was local only and is not part of PR for #6). The capability must be declared as data on the hook: the compile step infers api.read from ctx.api.object(...).find* patterns, but the retired // @capabilities comment directive is stripped by esbuild.
Scope note
Found while verifying #6; not fixed there (out of scope, different card). A sibling finding about ats_employer_member lacking the employer_org field the same stamp writes is filed separately.
Generated by Claude Code
What happens
On
origin/main(8293e45) every hook insrc/hooks/stamp.hook.tsis written againstctx.ql.find(...).objectstack build/objectstack devlower the inlinehandlerfunctions to sandboxed bodies (compile step: "Lowering inline handlers… all 6 callables are body-only"), and the QuickJS sandbox context has noql— cross-object reads there arectx.api.object(NAME).findOne({ where }), gated by theapi.readcapability (skillobjectstack-data,references/data-hooks.md, "What lives on the sandboxctx").Result under
pnpm dev: creating or updating anyats_employer_member,ats_job,ats_application,ats_interview,ats_offerorats_candidate_credentialrow through REST fails with HTTP 500 and the log shows(observed for
ats_employer_member_stamp,ats_job_stamp,ats_application_stampon a--freshin-memory boot of 17.3.0; the other three hooks have the same shape).pnpm validate/lint/typecheckare all green — the mismatch is a runtime-only fault, which is why it survived card 06.Why it matters
Nothing in the employer or transaction domain can be written through the API while this stands: no job, no application, no offer, no member. It blocks the M2 views (#2), the seed loader for #5 if it runs the hooks, and the runtime half of the M3 acceptance. The stamps are load-bearing for security (
employer_org/candidate_userfeed the row-level policies), so the fix is not "drop the hooks".Verified fix shape
A body-form stand-in (
body: { language: 'js', capabilities: ['api.read'], source: ... }usingctx.api.object('ats_employer').findOne({ where: { id } }),ctx.input,ctx.previous,!ctx.previousfor insert) made the same inserts succeed withemployer_org,candidate_useranddisplay_namestamped as designed — measured while verifying #6 (that stand-in was local only and is not part of PR for #6). The capability must be declared as data on the hook: the compile step infersapi.readfromctx.api.object(...).find*patterns, but the retired// @capabilitiescomment directive is stripped by esbuild.Scope note
Found while verifying #6; not fixed there (out of scope, different card). A sibling finding about
ats_employer_memberlacking theemployer_orgfield the same stamp writes is filed separately.Generated by Claude Code