You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Doc nit: skill.zod.ts's docblock still says an authored permissions key is "silently stripped" — SkillSchema now rejects it outright #7567
The prose and the behaviour disagree, and the prose is the wrong one.
packages/spec/src/ai/skill.zod.ts carries a NOTE paragraph in its docblock:
NOTE — there is deliberately NO per-skill permissions field. Access to AI capability is gated at the AGENT level (agent.access / agent.permissions, both enforced at the chat route), and each tool enforces its own authz when invoked. A permissions key authored on a skill is unknown to this schema and silently stripped at parse time — it grants and restricts nothing (ADR-0049: no unenforced security-shaped fields). Do not author one.
But SkillSchema no longer strips it — it refuses it, with a located message keyed on permissions in the same file:
permissions is not a skill key — skill invocation was never permission-gated, so this was stripped in silence and the author believed they had a gate. Gate at the AGENT instead (access / permissions on the agent, enforced since #1884), or on the underlying tools' actions.
The behaviour is strictly safer than the docblock claims (an author who writes permissions is now told, instead of quietly believing they have a gate — the exact hazard the refusal message describes). Only the docblock needs to change: it should say the key is rejected, and point at the refusal message, so nobody reads "silently stripped" and concludes the authoring surface is still lax.
Note the refusal message's own "this was stripped in silence" is fine as written — it narrates the historical reason for the refusal, not current behaviour. The NOTE paragraph is the one that states the stale present tense.
Still present on origin/main as of 2026-08-11.
Root cause
Documentation drift: the permissions key was moved onto the strict unknown-key refusal list without the surrounding docblock being updated in the same pass. Not a behaviour defect — no code change is required, only the prose.
Reproduction
Read the docblock paragraph beginning "NOTE — there is deliberately NO per-skill permissions field" in packages/spec/src/ai/skill.zod.ts.
Read the permissions: entry in the unknown-key refusal messages further down the same file.
Parse a skill authored with a permissions key: it is rejected with that message, not accepted-and-stripped as the docblock states.
Source
Extracted from the QA run #7463 (framework a86db17).
Findings triage: resolved the co-applied pm:queue + finding dual state → pm:queue, and re-routed domain:spec → domain:spec-surface (acceptance-surface criterion, standing instruction ③ / #6298 split): the fix is one docblock paragraph — every input that parses today parses byte-identically after it, so this is the contract's text face, not its semantic face. The refusal behaviour itself is correct and stays untouched.
Anchors verified on origin/main @ 34c01a5: stale present-tense "silently stripped at parse time" at packages/spec/src/ai/skill.zod.ts:243; the actual refusal message keyed on permissions at :284. Both as the card describes.
Grade: queue-class — concrete, scoped, verified-current; natural member for the spec-surface seat's next sweep card (one line in a truth-sweep clean-up, changeset patch).
Dup check: no open card owns this NOTE paragraph; the refusal-list work that introduced the drift is closed history.
Domain: domain:spec-surface (triage's routing: one docblock paragraph, every input parses byte-identically after it)
File surface: packages/spec/src/ai/skill.zod.ts (the NOTE paragraph, ~:243 at triage's 34c01a5) only — the refusal message at ~:284 is correct and stays; its historical "stripped in silence" narration is fine per the card
Container & model: os-dev subagent (in-process), model claude-sonnet-5 (S-mechanical: the card states the exact fix)
Serial constraints: nothing in flight touches skill.zod.ts; rounds 1–3 all closed.
Fix as the card specifies: rewrite the NOTE paragraph's stale present tense — the key is rejected outright with a located message, not silently stripped — and point at the refusal message so a reader doesn't conclude the authoring surface is lax. Report channel: issue comment here with the os-dev-report HTML-comment marker at draft-PR time.
{
"issue": 7567,
"status": "done",
"branch": "claude/issue-7567-skill-permissions-docblock",
"pr": "https://github.com/objectstack-ai/objectstack/pull/7610",
"premise_still_valid": true,
"summary": "Rewrote the stale NOTE paragraph in SkillSchema's docblock (packages/spec/src/ai/skill.zod.ts): it claimed an authored `permissions` key is 'silently stripped at parse time', but SkillSchema now REJECTS it outright via guidance.permissions with a located message. The paragraph now states the current truth and points at that refusal message; the refusal message itself (its 'stripped in silence' history narration) was left untouched per the card. Verified byte-identical parse behaviour (git diff touches only comment lines) and zero generated-docs drift.",
"tests": "pnpm --filter @objectstack/spec typecheck -> green (tsc + check:scripts-typecheck + check:test-typecheck all pass). pnpm --filter @objectstack/spec test -> 375 test files / 9840 tests passed, 0 failed (395s). node scripts/check-nul-bytes.mjs -> 'OK (scanned 7038 text file(s) ... no raw ASCII control bytes)'. pnpm --filter @objectstack/spec gen:schema then check:docs -> '231 generated files in sync with packages/spec' (zero diff; git status confirms no content/docs/** changes). Grepped for tests pinning the stale wording -- none found; skill.test.ts's existing test already asserts the rejection behaviour.",
"open_questions": [],
"out_of_scope_findings": []
}
Details
Before -> after (packages/spec/src/ai/skill.zod.ts, ~line 239-244):
Before:
> NOTE — there is deliberately NO per-skill permissions field. Access to AI capability is gated at the AGENT level (agent.access / agent.permissions, both enforced at the chat route), and each tool enforces its own authz when invoked. A permissions key authored on a skill is unknown to this schema and silently stripped at parse time — it grants and restricts nothing (ADR-0049: no unenforced security-shaped fields). Do not author one.
After:
> NOTE — there is deliberately NO per-skill permissions field. Access to AI capability is gated at the AGENT level (agent.access / agent.permissions, both enforced at the chat route), and each tool enforces its own authz when invoked. A permissions key authored on a skill is unknown to this schema and is REJECTED outright at parse time, with a located message keyed on permissions (see guidance.permissions below) pointing the author at the agent-level gate instead (ADR-0049: no unenforced security-shaped fields). Do not author one.
The refusal message at ~line 284 (guidance.permissions) was not touched — its "this was stripped in silence" phrasing narrates history and is correct as written, per the card.
git diff confirms only comment lines changed (2 removed / 4 added, both inside the block comment) — no schema, key, or behaviour change.
Docs-render measurement: The NOTE paragraph lives in the JSDoc block directly above export const SkillSchema, which is not the first docblock in the file (that's SkillTriggerConditionSchema's, per the #7113 positioning comment at the top of the file) — and build-docs.ts only renders the first docblock as the page blurb, plus each property's .describe() string into the properties table. Confirmed empirically: content/docs/references/ai/skill.mdx contains no occurrence of "silently stripped" or the NOTE text either before or after the change, and pnpm --filter @objectstack/spec check:docs (after a fresh gen:schema, since json-schema/ is gitignored) reports "231 generated files in sync with packages/spec" with zero diff. No content/docs/** files were touched or need to be.
Changeset: Added .changeset/skill-permissions-docblock-truth.md (patch, @objectstack/spec), following the #7444 / #7473 / #7565 precedent for describe/TSDoc-only spec docs fixes.
Labels: No skip-changeset label applied — this PR ships a changeset, so that label does not apply. Read PR #7610 back after opening; Auto Label bot had not yet applied any labels at read time.
Out-of-scope findings: none.
Open questions: none — the card's fix was fully mechanical and unambiguous.
PR left in draft; not flipped ready, auto-merge not armed, not waiting on CI (PM owns convergence per the current reporting contract).
Activity
os-zhuang commentedon Aug 11, 2026
Findings triage: resolved the co-applied
pm:queue+findingdual state →pm:queue, and re-routeddomain:spec→domain:spec-surface(acceptance-surface criterion, standing instruction ③ / #6298 split): the fix is one docblock paragraph — every input that parses today parses byte-identically after it, so this is the contract's text face, not its semantic face. The refusal behaviour itself is correct and stays untouched.origin/main@34c01a5: stale present-tense "silently stripped at parse time" atpackages/spec/src/ai/skill.zod.ts:243; the actual refusal message keyed onpermissionsat:284. Both as the card describes.changesetpatch).本评论来自分诊座位 Routine(#5474 试点),不构成认领。
Generated by Claude Code
os-help commentedon Aug 11, 2026
Claim — spec-surface seat (session
session_016R9de1FqP7NvwKvqXi92Gh, seat card #6298).pm:queue→pm:dispatched. Round 4.claude/issue-7567-skill-permissions-docblock../objectstack-issue-7567domain:spec-surface(triage's routing: one docblock paragraph, every input parses byte-identically after it)packages/spec/src/ai/skill.zod.ts(the NOTE paragraph, ~:243 at triage's34c01a5) only — the refusal message at ~:284 is correct and stays; its historical "stripped in silence" narration is fine per the cardclaude-sonnet-5(S-mechanical: the card states the exact fix)skill.zod.ts; rounds 1–3 all closed.Fix as the card specifies: rewrite the NOTE paragraph's stale present tense — the key is rejected outright with a located message, not silently stripped — and point at the refusal message so a reader doesn't conclude the authoring surface is lax. Report channel: issue comment here with the os-dev-report HTML-comment marker at draft-PR time.
Generated by Claude Code
os-help commentedon Aug 11, 2026
<!-- os-dev-report -->
{ "issue": 7567, "status": "done", "branch": "claude/issue-7567-skill-permissions-docblock", "pr": "https://github.com/objectstack-ai/objectstack/pull/7610", "premise_still_valid": true, "summary": "Rewrote the stale NOTE paragraph in SkillSchema's docblock (packages/spec/src/ai/skill.zod.ts): it claimed an authored `permissions` key is 'silently stripped at parse time', but SkillSchema now REJECTS it outright via guidance.permissions with a located message. The paragraph now states the current truth and points at that refusal message; the refusal message itself (its 'stripped in silence' history narration) was left untouched per the card. Verified byte-identical parse behaviour (git diff touches only comment lines) and zero generated-docs drift.", "tests": "pnpm --filter @objectstack/spec typecheck -> green (tsc + check:scripts-typecheck + check:test-typecheck all pass). pnpm --filter @objectstack/spec test -> 375 test files / 9840 tests passed, 0 failed (395s). node scripts/check-nul-bytes.mjs -> 'OK (scanned 7038 text file(s) ... no raw ASCII control bytes)'. pnpm --filter @objectstack/spec gen:schema then check:docs -> '231 generated files in sync with packages/spec' (zero diff; git status confirms no content/docs/** changes). Grepped for tests pinning the stale wording -- none found; skill.test.ts's existing test already asserts the rejection behaviour.", "open_questions": [], "out_of_scope_findings": [] }Details
Before -> after (packages/spec/src/ai/skill.zod.ts, ~line 239-244):
Before:
> NOTE — there is deliberately NO per-skill
permissionsfield. Access to AI capability is gated at the AGENT level (agent.access/agent.permissions, both enforced at the chat route), and each tool enforces its own authz when invoked. Apermissionskey authored on a skill is unknown to this schema and silently stripped at parse time — it grants and restricts nothing (ADR-0049: no unenforced security-shaped fields). Do not author one.After:
> NOTE — there is deliberately NO per-skill
permissionsfield. Access to AI capability is gated at the AGENT level (agent.access/agent.permissions, both enforced at the chat route), and each tool enforces its own authz when invoked. Apermissionskey authored on a skill is unknown to this schema and is REJECTED outright at parse time, with a located message keyed onpermissions(seeguidance.permissionsbelow) pointing the author at the agent-level gate instead (ADR-0049: no unenforced security-shaped fields). Do not author one.The refusal message at ~line 284 (
guidance.permissions) was not touched — its "this was stripped in silence" phrasing narrates history and is correct as written, per the card.git diffconfirms only comment lines changed (2 removed / 4 added, both inside the block comment) — no schema, key, or behaviour change.Docs-render measurement: The
NOTEparagraph lives in the JSDoc block directly aboveexport const SkillSchema, which is not the first docblock in the file (that'sSkillTriggerConditionSchema's, per the#7113positioning comment at the top of the file) — andbuild-docs.tsonly renders the first docblock as the page blurb, plus each property's.describe()string into the properties table. Confirmed empirically:content/docs/references/ai/skill.mdxcontains no occurrence of "silently stripped" or the NOTE text either before or after the change, andpnpm --filter @objectstack/spec check:docs(after a freshgen:schema, sincejson-schema/is gitignored) reports "231 generated files in sync with packages/spec" with zero diff. Nocontent/docs/**files were touched or need to be.Changeset: Added
.changeset/skill-permissions-docblock-truth.md(patch,@objectstack/spec), following the #7444 / #7473 / #7565 precedent for describe/TSDoc-only spec docs fixes.Labels: No
skip-changesetlabel applied — this PR ships a changeset, so that label does not apply. Read PR #7610 back after opening; Auto Label bot had not yet applied any labels at read time.Out-of-scope findings: none.
Open questions: none — the card's fix was fully mechanical and unambiguous.
PR left in draft; not flipped ready, auto-merge not armed, not waiting on CI (PM owns convergence per the current reporting contract).
Generated by Claude Code
Generated by Claude Code
docs(spec): state that skill.permissions is REJECTED, not silently st…