Skip to content

Stamp hooks throw in the sandboxed body runner (ctx.ql is undefined) — every REST insert/update on the six stamped objects returns 500 #12

Description

@claude

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

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions