Skip to content

skills(automation): state the wall-clock unit on the escalation row, and teach the hook-side runAs - #15681

Merged
os-zhuang merged 1 commit into
mainfrom
claude/issue-14483-automation-skill-wall-clock-and-hook-runas
Sep 5, 2026
Merged

skills(automation): state the wall-clock unit on the escalation row, and teach the hook-side runAs#15681
os-zhuang merged 1 commit into
mainfrom
claude/issue-14483-automation-skill-wall-clock-and-hook-runas

Conversation

@os-steve

@os-steve os-steve commented Sep 5, 2026

Copy link
Copy Markdown
Collaborator

Part of #14483
Fixes #14966

Two localized additions to one published skill file — skills/objectstack-automation/SKILL.md. skills/** is a governed surface, so this stays draft for a human merge.

Part of #14483 and not a closing keyword on purpose: that card carries two teaching points, and only point 1 lands here. Point 2 belongs in skills/objectstack-data/references/data-hooks.md, which is in flight on the spec lane's draft PR #15626 — the card stays open for it.

Addition 1 — the wall-clock unit on the escalation row (#14483, teaching point 1)

Ruling of record: maintainer 2026-08-31, mechanism B — the wall-clock unit is part of the declaration, not ambient prose. One clause, appended to the escalation cell of the ApprovalNodeConfigSchema table, immediately after the declaration it qualifies.

Before

| escalation | Optional per-node SLA — { enabled, timeoutHours, action: … }. escalateTo is a position machine name (expanded to its holders via sys_user_position, ADR-0090 D3) or a specific user id — never a membership tier. reassign without escalateTo degrades to notify (linted) |

After

| escalation | Optional per-node SLA — { enabled, timeoutHours, action: … }. timeoutHours is calendar (wall-clock) hours — nights, weekends and holidays count; the platform ships no business-hours calendar. escalateTo is a position machine name (expanded to its holders via sys_user_position, ADR-0090 D3) or a specific user id — never a membership tier. reassign without escalateTo degrades to notify (linted) |

Net lines: 0 — the row is one line before and one line after. Budget for this point was ≤2 lines net.

Every word is read off ApprovalEscalationSchema.timeoutHours (packages/spec/src/automation/approval.zod.ts:633), whose describe already reads "Calendar (wall-clock) hours before escalation triggers — nights, weekends and holidays count. The platform ships no business-hours calendar…". Schema and skill now say the same thing.

Addition 2 — the hook-side runAs paragraph (#14966)

The「Failure routing & runAs」section taught runAs for flows only. An author wanting to elevate a hook found no declared route in the published skill — and the only route it left visible, ctx.api.sudo(), is a TypeError in the sandbox. One paragraph, placed after the flow-side runAs material (the readonly / elevate-the-write / organization-pin blockquotes), in the neighbours' voice and wrap.

Before — nothing between the organization-pin blockquote and ### Filter tokens (config.filter).

After

A hook elevates itself with runAs, never with sudo. An object hook
(objectstack-data) declares its own runAs: 'system' | 'user' | 'inherit'
default 'inherit', the context of the write that fired it — scoping that
hook's ctx.api data operations only, on the in-process handler and the
sandboxed body alike. A 'user' hook whose trigger resolved no user has
nothing to scope to: its ctx.api data operations are refused
(HOOK_UNSCOPED_DATA_ACCESS, 403) rather than run unscoped — declare
runAs: 'system' when the elevation is intended. sudo is not a hook key.

Net lines: +9 (8 lines plus the blank separator).

Each fact verified against this tree, not against the card:

Claim in the paragraph Source on this tree
the three values, default 'inherit' packages/spec/src/data/hook.zod.tsrunAs: z.enum(['system','user','inherit']).default('inherit')
'inherit' = the context of the write that fired the hook same file, the runAs TSDoc and describe
scope is ctx.api data operations only describe: "Scope: ctx.api only — condition evaluation, the readonly strip on ctx.input, ctx.session and async are unchanged"
honoured on the in-process handler and the sandboxed body TSDoc: "Honoured on BOTH execution surfaces … at the one place both are wrapped (packages/objectql/src/hook-wrappers.ts wrapDeclarativeHook)"
'user' with no trigger user ⇒ data operations refused packages/objectql/src/hook-run-as.tsHOOK_UNSCOPED_DATA_ACCESS, status 403, thrown at the data door (the hook itself still runs)
sudo is not a hook key HookSchema guidance: "sudo is not a hook key. Declare runAs: 'system' …"

The paragraph states the rule and nothing else: no incident narrative, no PR or issue ids in the published text (check:doc-authoring rule 2).

Ratchet readings — the mechanical budget

The published catalog carries no line ratchet (scripts/pm/check-skill-line-ratchet.mjs excludes it by design); the token ratchet is the budget.

Reading Before After
skills/objectstack-automation/SKILL.md tokens (ceiling 12768) 12305 (headroom 463) 12491 (headroom 277)
skills/objectstack-automation/SKILL.md lines 951 960 (+9)
whole package — ratcheted (authored) tokens, ceiling 157650 140190 140376
whole package — bundle total (whole shipped tree) 151469 151655

No ceiling was raised and no other text was trimmed to pay for these.

Scope

skills/objectstack-data/references/data-hooks.md is untouched — it is in flight on PR #15626, and #14483's teaching point 2 lands there afterwards. No other skill, and no other file of any kind, is touched: the diff is one file, 10 insertions / 1 deletion.

skip-changeset: nothing is published from a package by this diff.

Gates — every verdict, exit code captured before any pipe

Family derived on this branch with node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack (21 commands; the tool read the change set from git itself). All green.

Gate Exit Verdict line
check-ci-filter-parity 0 OK: all 148 declared cross-package glob(s) (102 unique) are covered …
check-closing-keyword-parity 0 OK (3 parsers agree on all 9 keywords …; 5 file(s) carrying the grammar across 7586 tracked files, all registered)
check-closing-keyword-parity --self-test 0 24 assertions, 5 mutations of the shipped parsers each driven to red
check-comment-mask-corpus 0 5969 files, 0 disagree, 0 unparseable
check-skills-token-ratchet 0 skills/objectstack-automation/SKILL.md is 12491 tokens (ceiling 12768; headroom 277); 36 authored bundle file(s) within their ceilings
check-skills-token-ratchet --self-test 0 64 cases pass
@objectstack/lint check:doc-formula-expressions 0 9 @example(s) judged clean across 1178 spec files; 14 field-level predicates clean (first run exited 3 — PREREQUISITE NOT MET, @objectstack/formula and @objectstack/lint unbuilt; re-run green after building both — the 3 measured nothing)
@objectstack/spec check:skill-docs 0 skills/README.md, content/docs/ai/skills-reference.mdx in sync
check:agent-test-spelling 0 0 violations — 450 file(s)
check:corpus-claim-drift 0 OK, no new claim sites beside a pinned spelling
check:cross-package-test-inputs 0 OK: 27 package(s) read outside themselves, all declared
check:doc-authoring 0 14808 customer-facing string(s) clean — no internal issue-id references; sibling-package prose ids hold the baseline
check:driver-memory-census 0 OK — every declaration ledgered, every ledger entry live
check:nul-bytes 0 OK (7579 text file(s); no raw ASCII control bytes)
check:pm-governed-merges 0 self-test 263 assertions; audit clean
check:refd-timer-probe 0 5964 source file(s) swept
check:role-word 0 OK, no new occurrences of the reserved word
check:skill-compatibility 0 11 SKILL.md file(s) reconciled against 79 workspace packages
check:skill-frame-sync 0 2 copies of the decision frame are structurally isomorphic
check:skill-identifier-liveness 0 Leg 1: 465 citation(s) over 46 published file(s); Leg 2: 8 registered exhaustive section(s), 0 ledgered gaps
check:watch-hint-literal 0 58 declaration(s) across 4 rostered name(s)

Three more run beyond the derived family, because a SKILL.md body changed:

Gate Exit Verdict
@objectstack/spec check:skill-refs 0 clean (no regeneration owed)
check:pm-skill-ratchet 0 every ceiling held; the published catalog is outside this ratchet by design
check:skill-frame-freshness 0 the decision frame in this tree is current with origin/main

pnpm lint — a measured narrowing, not a skip. ESLint's own configuration supplies no matching configuration for this path: pnpm exec eslint --no-inline-config --format json skills/objectstack-automation/SKILL.md returns exactly one result whose only message is "File ignored because no matching configuration was supplied" — 1 file addressed, 0 rules applied. The diff adds no TS/JS file and edits no eslint config, tsconfig or source, and type-aware linting is not in play here, so no untouched file's verdict can move either. A repo-wide eslint . would read zero lines of this change; CI runs it regardless.

All gate readings above were taken on abbbc3cc8, which is this branch's head.

🤖 Generated with Claude Code

Generated by Claude Code


Generated by Claude Code

…hook-side `runAs` paragraph

Two localized additions to the published automation skill.

1. The `escalation` row of the per-node table now states the unit with the
   declaration: `timeoutHours` is calendar (wall-clock) hours — nights,
   weekends and holidays count, and the platform ships no business-hours
   calendar. Mirrors the `ApprovalEscalationSchema.timeoutHours` describe, so
   the schema and the skill read the same.

2. The "Failure routing & `runAs`" section carried flow-side `runAs` only. It
   now also states the hook-side rule: a hook declares its own
   `runAs: 'system' | 'user' | 'inherit'` (default `'inherit'`, the context of
   the write that fired it), scoping that hook's `ctx.api` data operations only
   on both the in-process handler and the sandboxed body; a `'user'` hook with
   no trigger user has its data operations refused rather than run unscoped;
   `sudo` is not a hook key. Every fact is read off `HookSchema.runAs` in
   `packages/spec/src/data/hook.zod.ts`.

No other file moves; `skills/objectstack-data/references/data-hooks.md` is
deliberately untouched.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019RfFHiRCSs3JXLK4cwcfox
@claude claude Bot added the skip-changeset PR has no user-facing published change; bypasses the changeset gate label Sep 5, 2026
@github-actions github-actions Bot added size/s documentation Improvements or additions to documentation labels Sep 5, 2026

os-steve commented Sep 5, 2026

Copy link
Copy Markdown
Collaborator Author

ACCEPT — governed in-seat review of PR #15681, head abbbc3cc8 (skills seat, 2026-09-05T03:1xZ).

Implemented-by: claude/issue-14483-automation-skill-wall-clock-and-hook-runas (os-dev subagent; #14483 chain head, #14966 folded)
Reviewed-by: session_019RfFHiRCSs3JXLK4cwcfox (skills seat)

Verified in a detached compare worktree at the PR head against merge-base 5eb24f867, not from the report: one file, skills/objectstack-automation/SKILL.md, +10 / −1. (1) The escalation row of the approval-node table now states, next to timeoutHours, that the number is calendar (wall-clock) hours — nights, weekends and holidays count, no business-hours calendar ships — the same wording the schema's own describe carries; net 0 lines against the +2 budget. (2) One blockquote paragraph after the flow-side runAs material: a hook elevates itself with runAs, never sudo; the three values with 'inherit' as the default; ctx.api data operations only, on the inline handler and the sandboxed body alike; a 'user' hook whose trigger resolved no user has its ctx.api data operations refused (HOOK_UNSCOPED_DATA_ACCESS, 403) rather than run unscoped. Every fact re-read against packages/spec/src/data/hook.zod.ts and packages/objectql/src/hook-run-as.ts on the head; the one wording correction the dev took from the schema (the refusal lands on the data operations, not on the hook run) is right. No issue ids, no dates, no incident narrative, no model names in the added text. data-hooks.md untouched, so Part of #14483 is correct and teaching point 2 stays on that card behind the spec lane's PR #15626. Seat reading on the head: check-skills-token-ratchet 12491 tokens (ceiling 12768; headroom 277). The dev's family: 21 derived commands all exit 0 plus check:skill-refs, check:pm-skill-ratchet, check:skill-frame-freshness; check:doc-authoring 14808 strings clean; check:skill-identifier-liveness and check:skill-compatibility green. CI on abbbc3cc8: 34 check runs, 23 success, 11 skipped, none failing.

Landing regime: governed (skills/**) ⇒ the PR stays a draft; reviewers os-zhuang + hotlong requested in this same act; human merge. Closes #14966; #14483 stays open for its second teaching point.


Generated by Claude Code

@os-zhuang
os-zhuang marked this pull request as ready for review September 5, 2026 03:22
@os-zhuang
os-zhuang enabled auto-merge September 5, 2026 03:23
@os-zhuang
os-zhuang added this pull request to the merge queue Sep 5, 2026
Merged via the queue into main with commit 036b4ca Sep 5, 2026
36 checks passed
@os-zhuang
os-zhuang deleted the claude/issue-14483-automation-skill-wall-clock-and-hook-runas branch September 5, 2026 04:02
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation size/s skip-changeset PR has no user-facing published change; bypasses the changeset gate

Projects

None yet

Development

Successfully merging this pull request may close these issues.

finding(skills): skills/objectstack-automation documents runAs for flows only — a hook-side runAs paragraph is owed once PR #14915 lands

3 participants