Skip to content

spec: declare grantedPermissions on EnvironmentArtifactSchema (record of manifest id → PluginPermissions) — the artifact-contract half of #14034 / #11333 Phase 1, which must land BEFORE cloud can write it (plain z.object strips undeclared keys at the artifact door) #14865

Closed

Description

@baozhoutao

Filed by the repo:cloud execution seat (seat post #6026, session session_017Wu48ZSjk7HJjyJmGNBtUk, R35) as the framework half that #14034's measurement (comment on that card, 2026-09-03) showed must land first. Triage: domain:* and type are yours — expected domain:spec; Clause-② yes (a new key on a published contract face) ⇒ CONTRACT_REVIEW_TIER. Dedup: no open objectstack card declares a granted-permissions field on the environment artifact (granted across packages/spec/src returns zero permission declarations at pin 655b106c; the requested set is manifest.zod.ts:290-291).

Ruling this executes

#11333 option A (2026-08-30 「同意」) and the #13457 batch ruling (2026-09-01 「同意」): the consented four-class permission set {services, hooks, network, fs} rides the plugin artifact contract; cloud (owner of the install-consent flow) writes it at consent-compile time, objectstack's loader consumes it from the environment-local carrier at materialize time (ADR-0003 / cloud ADR-0007: sys_package_installation is never read on a runtime path).

Measured on cloud 2f469233 × pin 655b106c (details on #14034)

  • The consent flow writes the granted set to exactly one place: sys_package_installation.granted_permissions (cloud routes/package-install.ts:640-652, written :667/:678), preserving absent vs empty (:651-653 emits it only when consent-bearing).
  • The compiled environment artifact envelope (cloud routes/cloud.ts:747-757: schemaVersion, environmentId, commitId, checksum, metadata, functions, manifest, builtAt, builtWith, runtime) does not carry it; the marketplace-install merge loop reads the install row but consumes only enabled / package_version_id / with_sample_data.
  • The env-local carrier (env-artifact-cache.ts:88-102) stores JSON.stringify(artifact) verbatim ⇒ once the envelope carries the key, the carrier carries it for free — the ruling's carrier assumption holds.
  • The consumer at the pin wants a per-plugin map: registerGrantedPermissions(pluginName, PluginPermissions) (packages/core/src/security/plugin-permission-enforcer.ts:102-111), value shape PluginPermissionsSchema (packages/spec/src/kernel/manifest.zod.ts:32-44, .strict()).
  • Pin-lag / silent-stripping: EnvironmentArtifactSchema (packages/spec/src/system/environment-artifact.zod.ts:83) and ManifestSchema (manifest.zod.ts:158) are plain z.object, so an undeclared key written by cloud is stripped at objectstack's artifact door (packages/metadata/src/plugin.ts:900-925, parse at :908/:915). A cloud-first landing would fail silently. ⇒ spec first, pin bump, cloud second.

The ask (spec half)

Declare one top-level envelope key on EnvironmentArtifactSchema:

grantedPermissions: z.record(z.string(), PluginPermissionsSchema).optional()

Coupling

Refs: #14034 · #11333 · #13457 · #13458 · ADR-0003 / cloud ADR-0007 · ADR-0025.

Activity

  1. added
    priority:p1High: required for production / M2
    domain:specDomain lane — seat card indexed by label:pm:seat
    pm:blockingDerived cache from the Blocked-by reverse index: open card with open dependents — never hand-set
    needs:contract-reviewMarker only — PR awaits its at-tier Contract review PASS; no check reads it, the record decides
    on Sep 3, 2026
  2. huangyiirene commented on Sep 3, 2026

    @huangyiirene
    Collaborator

    Triage — sweep repair + graded. pm:queue (kept) · pm:blocking · domain:spec · priority:p1 · needs:contract-review.

    Verified at origin/main — all three load-bearing premises:

    • EnvironmentArtifactSchema is a plain z.object (environment-artifact.zod.ts:83) ⇒ the silent-stripping story is real: a key cloud writes before this lands is dropped at the artifact door, with no error. That is what makes the ordering (spec first, pin bump, cloud second) a correctness requirement rather than a preference.
    • grantedPermissions → 0 occurrences in packages/spec/src. Not yet declared.
    • PluginPermissionsSchema exists at kernel/manifest.zod.ts:33 ⇒ the value shape is available and needs no new type.

    p1: it is the head of a two-deep cross-repo chain — #14034 (repo:cloud) is pm:blocked on this, and #13457 is blocked on #14034 — and the direction is already ruled (#11333 option A, 2026-08-30 「同意」; #13457 batch, 2026-09-01 「同意」). ⛔ Nothing downstream can start until this lands, so grading it below the work it gates would strand both.

    pm:blocking applied as the derived cache for that edge. needs:contract-review on the Clause ② mechanical floor — a new key on a published contract face is always yes; the filer declared it and I am confirming rather than re-deriving.

    Scope — as filed, with three things ⛔ not to negotiate:

    ⚠️ This card is the spec half only. ⛔ Do not write the cloud side, ⛔ do not touch env-artifact-cache.ts (it stores JSON.stringify(artifact) verbatim, so it carries the key for free once declared — the ruling's carrier assumption, and I confirmed the card's reasoning for it stands).

    ✅ Dedup accepted; the zero was paired with a named pin (655b106c) and a positive locator for the requested set.


    Generated by Claude Code

  3. zhuangjianguo commented on Sep 3, 2026

    @zhuangjianguo
    Collaborator

    Claim: PM loop, domain:spec seat (seat post #6017), R1 of this shift
    Session: session_0174WZTU6XcFcS7g2kykC53i (GitHub zhuangjianguo)
    Branch: claude/issue-14865-environment-artifact-granted-permissions
    Worktree: objectstack-issue-14865
    Domain: domain:spec
    File surface: packages/spec/src/system/environment-artifact.zod.ts (one new optional top-level envelope key grantedPermissions: z.record(z.string(), PluginPermissionsSchema).optional() + docblock naming producer and consumer; ⛔ absent vs {} preserved; key = manifest id) · packages/spec/src/system/environment-artifact.test.ts (pins: declared key survives parse; absent and {} round-trip distinctly; an unknown sibling key is still stripped; the manifest-id-vs-package_id residual risk pinned as a stated assumption) · packages/spec/src/kernel/manifest.zod.ts:19-31 (doc-only carrier sentence pointing at the new key; ⛔ no schema change there) · whatever generated artifacts check:generated proves stale (json-schema manifest / references page for the artifact schema) · a liveness-ledger row only if system/environment-artifact is a governed type in packages/spec/liveness/* (the dev measures; a cross-repo producer is written into the note if a row exists) · .changeset/*.md (@objectstack/spec minor — a new authorable/published key). Stop on breach; explain in the report. ⛔ Nothing in packages/metadata, no cloud-side work, no env-artifact-cache.ts.
    Container & model: S/M (the schema line is short; the pins and the ledger question are the work), mode:subagent, model: fable (CONTRACT_REVIEW_TIER). node scripts/pm/dispatch-gates.mjs --tier packages/spec/src/system/environment-artifact.zod.ts packages/spec/src/kernel/manifest.zod.ts at origin/main 9786d39 (15:40Z): "no path-derived mandate: the surface hits none of the 3 declared glob(s)" + "Clause ② SUSPECT surface — a hint, not a verdict" on both paths (packages/spec/src/**); the tier is decided from the card content — a new key on a published contract face is the mechanical floor for clause ②.
    Clause-②: yes
    Serial constraints cleared: open-PR file scan at 15:38Z (22 open PRs, every file list read via REST) — none touches environment-artifact.zod.ts, environment-artifact.test.ts or kernel/manifest.zod.ts; last commit on those files 4d0d9445 (#14714, 2026-09-02T21:14Z) — no same-day churn; lane siblings in flight (#14010 merge round on hook.zod.ts / objectql; #14722 on stack.zod.ts) are file-disjoint; H17 on-hold trigger-file index (#9857, swept 13:47Z) names none of these files. Downstream Blocked-by: edge: #14034 (repo:cloud) waits on this card (the pm:blocking cache); #13457 behind that.

    Decision re-read (all comments read, one — triage 5522848160): rulings in force are #11333 option A (2026-08-30 「同意」) and the #13457 batch ruling (2026-09-01 「同意」), quoted in the card body; triage's three non-negotiables (absent ≠ empty; key on manifest id; ship both pin tests) go into the dispatch as rulings. Premise re-read on origin/main 9786d39 (15:40Z): grantedPermissions → 0 hits under packages/spec/src (positive control: PluginPermissionsSchema → 4 hits in kernel/manifest.zod.ts); EnvironmentArtifactSchema = lazySchema(() => z.object({ at environment-artifact.zod.ts:83 — a plain object, so the silent-strip premise stands. Premise holds.


    Generated by Claude Code

  4. added
    pm:dispatchedDispatched to a dev agent by /pm-dispatch
    and removed
    pm:queueReady for the PM dispatch loop
    on Sep 3, 2026
  5. self-assigned this
    on Sep 3, 2026
  6. zhuangjianguo commented on Sep 3, 2026

    @zhuangjianguo
    Collaborator

    Dispatch (R1 of this shift, 2026-09-03T16:05Z) — domain:spec seat, session_0174WZTU6XcFcS7g2kykC53i, seat post #6017. mode:subagent, model: fable (CONTRACT_REVIEW_TIER), size S/M, Clause ② yes. The dev leaves its own Claim: comment below (session + branch + Clause-②: yes + its complete file surface); the assignee stays the PM's.

    Dispatch text handed to the dev, in brief (the two rulings and triage's three non-negotiables are quoted verbatim and marked non-renegotiable; scope = the spec half only):

    • One optional top-level key grantedPermissions: z.record(z.string(), PluginPermissionsSchema).optional() on EnvironmentArtifactSchema, describe naming producer (cloud consent-compile) and consumer (materialize-time loader), absent ≠ {} stated and pinned; key = manifest id, residual risk pinned as the documented assumption.
    • Hypotheses to measure: H1 plain-object strip today / declared-key survival + unknown-sibling strip after (schema-reachable pins); H2 the PluginPermissionsSchema import has no cycle; H3 which generated artifacts move (check:generated --fix, never hand-edited); H4 whether the artifact type is liveness-governed (row only if so, cross-repo scope spelling); H5 the doc-only carrier sentence in manifest.zod.ts; H6 changeset minor.
    • Landing: draft PR with Fixes #14865 + needs:contract-review on the PR in the same stroke (the card already carries it — dual carrier); ⛔ no ready flip. In-seat contract review at tier by this seat on delivery.

    Generated by Claude Code

  7. claude commented on Sep 3, 2026

    @claude
    Contributor

    Claim: os-dev subagent for #14865 (dispatched by the domain:spec seat, seat post #6017, 2026-09-03T16:05Z)
    Session: session_0174WZTU6XcFcS7g2kykC53i (GitHub zhuangjianguo)
    Branch: claude/issue-14865-environment-artifact-granted-permissions (pushed empty at BASE 3386493f)
    Worktree: objectstack-issue-14865
    Clause-②: yes
    Mode: mode:subagent, model: fable (CONTRACT_REVIEW_TIER)

    File surface (complete):

    • packages/spec/src/system/environment-artifact.zod.ts — one new optional top-level envelope key grantedPermissions: z.record(z.string(), PluginPermissionsSchema).optional() with a describe/docblock naming the producer (cloud consent-compile) and the consumer (the materialize-time loader), stating absent vs {}; key = the plugin manifest id.
    • packages/spec/src/system/environment-artifact.test.ts — schema-reachable parse pins: the declared key survives and round-trips; absent stays absent; {} stays {}; an unknown top-level sibling is still stripped (positive control); the value shape is enforced through the strict PluginPermissionsSchema; the manifest-id-vs-package_id residual risk pinned as the documented assumption on the key's meaning.
    • packages/spec/src/kernel/manifest.zod.ts lines 19-31 — a doc-only carrier sentence pointing at the new key; no schema change there.
    • Whatever generated artifacts check:generated --fix proves stale after pnpm --filter @objectstack/spec build (json-schema manifest shard / references page / api-surface if the exported type surface moves) — never hand-edited.
    • .changeset/environment-artifact-granted-permissions.md — @objectstack/spec minor.
    • packages/spec/liveness/* — a row only if system/environment-artifact measures as a liveness-governed type (measured in-run; reported either way).

    Out of scope, not touched: packages/metadata, packages/core, any cloud-side work, env-artifact-cache.ts.

    Premise re-read on origin/main 3386493f (16:10Z): EnvironmentArtifactSchema = lazySchema(() => z.object({ at environment-artifact.zod.ts:83 (a plain object, so the silent-strip premise stands); grantedPermissions 0 hits under packages/spec/src (positive control: PluginPermissionsSchema present at kernel/manifest.zod.ts:33, .strict()); the card body ends with its Refs: line (not truncated); all three comments read (triage 5522848160, PM claim 5528213556, dispatch 5528286028). Premise holds.

    Generated by Claude Code


    Generated by Claude Code

  8. claude commented on Sep 3, 2026

    @claude
    Contributor

    os-dev-report

    {
      "issue": 14865,
      "status": "done",
      "branch": "claude/issue-14865-environment-artifact-granted-permissions",
      "pr": "https://github.com/objectstack-ai/objectstack/pull/14992",
      "head": "84ce33e9 (BASE 3386493f; commits 7b05b01b feat, d4437273 docs regen, 304318e8 pure control test, 84ce33e9 north-star patch round)",
      "premise_still_valid": true,
      "summary": "Declared one optional top-level envelope key, `grantedPermissions: z.record(z.string(), PluginPermissionsSchema).optional()`, on `EnvironmentArtifactSchema`, with a docblock and describe that name the producer (cloud consent-compile, from `sys_package_installation.granted_permissions`) and the consumer (the materialize-time loader via `PluginPermissionEnforcer.registerGrantedPermissions`), state absent (no consent record) vs `{}` (consented to nothing) as never collapsed (no default), key = plugin manifest `id` (not `package_id`) with the residual risk written out, and note the key sits beside `metadata`, outside the checksum digest. 13 schema-reachable parse pins plus one pure sibling-strip control; a doc-only carrier sentence on `PluginPermissionsSchema`; the generated references page regenerated by `check:generated --fix` (the only generated artifact that moved); the seat's patch round added one bullet to `content/docs/concepts/north-star.mdx`; changeset `@objectstack/spec` minor. Premise verified at `origin/main` 3386493f (BASE): plain `z.object` at environment-artifact.zod.ts:83, 0 hits for `grantedPermissions` under packages/spec/src, `PluginPermissionsSchema` strict at kernel/manifest.zod.ts:33, card body ends with its Refs line, all comments read. Hypotheses: H1 holds (ablation in `tests`); H2 no cycle — a static walk of kernel/manifest.zod.ts's 28-file relative-import closure reaches neither system/environment-artifact.zod.ts nor stack.zod.ts, and the built ./system, ./cloud and ./kernel entries (CJS and ESM) load and expose the key; H3 exactly one generated file moved (content/docs/references/system/environment-artifact.mdx, +7) — authorable-surface and its .base.json anchor, json-schema manifest, api-surface, export-origins and declaration-map unchanged (the envelope is lazy, not authorable, adds no export); H4 not liveness-governed (absent from GOVERNED, SPEC_ONLY_SCHEMAS and the metadata-type registry; no ledger file) — no row added, check:liveness green; H5 comment-only, no schema diff in manifest.zod.ts, its describe untouched so the kernel/manifest reference page did not move; H6 minor, changeset gates green (non-breaking, no ADR-0087 marker owed). ENVIRONMENT_ARTIFACT_SCHEMA_VERSION stays 0.1 (additive optional key). Notes for the seat: my REST writes land as `claude[bot]` (App installation token), not the seat's login — the Claim comment is 5528311175 (posted 15:49Z before the first edit); `needs:contract-review` was hung on the PR at creation per the dispatch, before the patch-round message arrived, and no label was written afterwards; dispatch-gates warned STALE TREE (8+ commits behind origin/main at derivation) — no merge of main was done (the PM's open-PR scan read the files as disjoint; the queue rebuilds on main).",
      "tests": "vitest `pnpm --filter @objectstack/spec exec vitest run --maxWorkers=2 src/system/environment-artifact.test.ts` on 304318e8 (the last commit touching tests or source; 84ce33e9 is one .mdx): `Tests 27 passed (27)` = 14 pre-existing + 13 new. `pnpm --filter @objectstack/spec typecheck` on 304318e8: `tsc --noEmit`, check:scripts-typecheck and `check:test-typecheck: OK` all green; `tsc -p tsconfig.test.json --listFiles` lists src/system/environment-artifact.test.ts (1 hit) and 0 errors mention the edited files. Reverse verification, fix committed first, source-resolved (the test imports ./environment-artifact.zod relatively and packages/spec/vitest.config.ts declares no alias — no build in either leg, dist untouched throughout): Leg A restored the BASE schema file with `git checkout BASE -- PATH` (on disk: grep -c grantedPermissions = 0, hash-object equals the BASE blob 6ae63da0) — `11 failed | 16 passed (27)`, the 11 being every pin that needs the key, the 16 = 14 pre-existing + the pure control + absent-stays-absent; Leg B injected `.passthrough()` with perl (on disk: 1 match, hash 0ce2c7a1 differs from the HEAD blob 09ce094c) — `2 failed | 25 passed (27)`, exactly the two sibling-strip controls; restore via `git checkout HEAD -- PATH` (absolute path, trap on EXIT INT TERM) proven after each leg by hash-object == HEAD blob 09ce094c and `git diff HEAD -- PATH` empty (porcelain 0 lines); control leg on unmodified HEAD `27 passed (27)`. Direction observed = direction predicted for both legs. The rebuilt dist on 304318e8 carries `grantedPermissions: z.ZodOptional...` in dist/environment-artifact.zod-*.d.ts (fresh declarations, not a cache). eslint --no-inline-config on the 3 edited TS files: exit 0 (a scoped reading, not the repo-wide run).",
      "gates": "Derived on the actual diff, no paths (`node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack`; first derivation at 7b05b01b gave 58 commands, the re-derivation after the docs-regen commit gave 76 — the 18 docs families were run as a delta and `--ran` on the final list reads `76 derived, 76 run, 0 UNRUN`; every exit code captured before any pipe, one log per gate). On 304318e8: 71 green by their own verdict lines, among them `check:generated` after the rebuild `All 15 generated artifacts are up to date`, check:api-surface, check:authorable-surface, check:docs, check:export-origins, check:dual-source-exports, check:exported-any, check:liveness, check:strictness-ledger, check:skill-examples (`256 prose examples type-check across 3 surface(s)` after building the client-react closure), check:changeset-no-major, check:empty-changeset, check:adr-0087-registration, check:nul-bytes, check:cross-package-test-inputs, check:test-source-alias, check:type-check-coverage. NOT MEASURED, each by its own PREREQUISITE NOT MET / not-built text (none a finding): check-test-completeness (grades a saved turbo test log), check-dev-prereqs (66 of 67 packages have no dist — whole workspace not built), check:dual-build-cjs-loads and check:type-check-debt (need the built workspace closure). Patch round on 84ce33e9: the 34 families dispatch-gates derives for content/docs/concepts/north-star.mdx (all already in the union) re-run — 33 green including check:doc-anchors, check:doc-authoring, check:docs-single-h1, check:nul-bytes, check:docs, check:doc-frontmatter, check:section-landing-index, and lint check:doc-formula-expressions / check:doc-security-posture (measured green now that the lint closure exists); check-test-completeness NOT MEASURED. CI status at report time: in_progress (not polled).",
      "deviations": "(1) Whole-tree gates needing the built workspace (check-dev-prereqs, check:dual-build-cjs-loads, check:type-check-debt) and the test-log grader (check-test-completeness) NOT MEASURED locally — CI's run; scoped equivalent for the one package this diff touches: the built ./system, ./cloud and ./kernel entries load in CJS and ESM and expose the key. (2) Downstream consumer typecheck beyond @objectstack/spec NOT MEASURED locally (the prefix direction `...@objectstack/spec` is the whole repo): the change is an additive optional key on an inferred type — widening only — and the two named consumer sites (packages/metadata/src/plugin.ts spread-parse; packages/core's `PluginPermissions as GrantedPermissions` alias) reference no envelope key by name; the required TypeScript Type Check job covers it. (3) Repo-wide `pnpm lint` not run (CI's); the 3 edited TS files were linted, reported as a scoped reading, not as a narrowed measurement. (4) vitest/typecheck not re-run on 84ce33e9: that commit changes one .mdx no test or tsc program reads. (5) Five dist-reading spec gates went red once on a re-run because dist's mtime was older than src after the test commit and the ablation checkouts; rebuilt on 304318e8, all green — completeness, not a finding. (6) PR body PATCHed once to record the patch-round head (the platform normalises the session footer to bare on edit; attribution lives in the body prose). (7) `check-half-states` ran twice as derived (371 s and 380 s, 100+ REST reads each) — see findings.",
      "files_changed": [
        "packages/spec/src/system/environment-artifact.zod.ts (+63): import PluginPermissionsSchema; the key with docblock + describe; header Boundary bullet",
        "packages/spec/src/system/environment-artifact.test.ts (+146): 13 pins + 1 pure control under `grantedPermissions — install-time granted set per plugin manifest id (#14865)`",
        "packages/spec/src/kernel/manifest.zod.ts (+8): doc-only carrier sentence on PluginPermissionsSchema; no schema change",
        "content/docs/references/system/environment-artifact.mdx (+7): regenerated by check:generated --fix, never hand-edited",
        "content/docs/concepts/north-star.mdx (+4): one bullet in the Environment Artifact contents list (seat's patch round)",
        ".changeset/environment-artifact-granted-permissions.md (+47): @objectstack/spec minor"
      ],
      "mcp_calls": "0 — no mcp__github__* call was made; the card body and all comments were read via the repo-scoped REST endpoints (probe GET /repos/.../issues/14865 answered 200 at 16:08Z — channel open for this session), the Claim comment, PR creation, the one label add and this report went via REST POST/PATCH, everything else via git.",
      "open_questions": [
        {
          "question": "`grantedPermissions` sits beside `metadata`, so it is outside the `checksum` digest (documented as the digest of the canonical `metadata` serialization only). Is integrity over the granted set wanted on the envelope? Informational for the contract review; not blocking.",
          "options": [
            "A — no change: consent state is control-plane state re-emitted per assembly, consistent with the ruling's carrier reading; the fact is documented on the key, in the header Boundary bullet and on north-star",
            "B — a separate later decision: widen the digest or add a second digest over the granted set; not this key"
          ],
          "recommendation": "A, because the ruling places the set on the envelope as a carrier and the checksum contract as written is over `metadata`; changing digest semantics is its own contract decision with its own consumer (the loader's checksum verification) and belongs to no card in this chain."
        }
      ],
      "out_of_scope_findings": [
        "NOT FILED — handed to PM for filing (PM tooling; the dedupe channel was deliberately not spent): scripts/pm/dispatch-gates.mjs names scripts/pm/check-half-states.mjs in its `declared WHOLE-TREE and named on every card` set; it is a report-only board patrol (its own footer: `Report-only: findings are patrol input, not a gate verdict`) that took 371 s and 380 s and 100+ REST reads per run here, so every dev card that runs the derived family pays it with no verdict to read. Suggest excluding report-only patrols from the per-card family or marking them PM-only.",
        "Observation, not filed: the built ./cloud and ./system entries carry separate runtime copies of the envelope schema (tsup.config.ts `splitting: false`, per-entry self-contained bundling, `#8133 stays on hold` in that file) — cross-entry runtime identity was never a property of dist, and the declaration-level single source (#4740 pin) holds. Pre-existing, unchanged here."
      ]
    }

    Generated by Claude Code


    Generated by Claude Code

  9. zhuangjianguo commented on Sep 3, 2026

    @zhuangjianguo
    Collaborator

    Contract review — PASS (in-seat, at tier) · ACCEPT — PR #14992 (head 84ce33e9, Fixes #14865). domain:spec seat, session_0174WZTU6XcFcS7g2kykC53i (seat post #6017), 2026-09-03T16:58Z. Fuse: last_served_model = claude-fable-5-1 = CONTRACT_REVIEW_TIER (read 15:29Z). Provenance: the 2026-08-31 ruling (in-seat contract review by the dispatching lane).

    Report: os-dev-report 5529148343 (status: done, premise_still_valid: true; source/test readings on 304318e8, the docs patch round on 84ce33e9 — the final head differs from the tested head by one .mdx only, verified by this seat: git diff 304318e8..84ce33e9 = content/docs/concepts/north-star.mdx +4). Dev claim 5528311175 (15:49Z, Clause-②: yes; posted as claude[bot], the App-token identity — the branch is the identity, per the claim rules). Reviewed against the card, triage 5522848160, the full diff on GitHub and this seat's readings of origin/main.

    ① Derived judgments (Clause ② yes — a new key on a published contract face): the accept set widens by exactly one optional top-level key on EnvironmentArtifactSchema, grantedPermissions: z.record(z.string(), PluginPermissionsSchema).optional(); the value is the strict PluginPermissionsSchema by identity (pinned: record.valueType is the same object; an unknown permission class is refused with unrecognized_keys at grantedPermissions.PLUGIN); an unknown top-level sibling is still stripped (two controls, one with no grantedPermissions present at all); absent ≠ {} is carried by the schema — no .default({}), both readings round-trip and stay distinguishable on the parsed value; key = manifest id, with the residual risk (a package_id-shaped key parses and is never looked up) documented on the key and pinned on its description text. Emitted shape: an artifact without the key parses exactly as before (the 14 pre-existing pins unchanged); ENVIRONMENT_ARTIFACT_SCHEMA_VERSION stays 0.1 — additive and optional, correct. Public surface: no new export (api-surface, export-origins, declaration-map unchanged; the envelope is not in the authorable surface); the only generated artifact that moves is the reference page (check:generated 15/15 after --fix). Ruling fidelity — #11333 option A / #13457 batch (carrier declared on the envelope; cloud writes at consent-compile; the loader consumes at materialize) — met; triage's three non-negotiables (absent ≠ empty; manifest id; both pin tests shipped) — met.
    ② Semver / changeset: @objectstack/spec minor — correct for a new published key; check:changeset-no-major, check:empty-changeset and check:adr-0087-registration green (non-breaking, no marker owed).
    ③ Boundary flags: (a) grantedPermissions sits beside metadata, outside the checksum digest — the dev's open question, answered A (no change here: the ruling places the set on the envelope as a carrier and the digest contract as written covers metadata); the gap is filed for triage as #14993 (a security-boundary question is the maintainer's, not this PR's); (b) per-entry runtime copies of the envelope in dist (splitting: false) — pre-existing, #8133 on hold; (c) check-half-states inside the per-card gate family (371 s / 380 s and 100+ REST reads per run) — already filed as #14899 by an earlier dev, not re-filed.

    Checklist: draft; Fixes #14865 on line 1 (the spec half completes the card; #14034 / #13457 are named without closing keywords and stay open); scope = the claimed files plus the seat's patch-round bullet in north-star.mdx (the drift bot named the page; this seat verified it enumerates the envelope's contents and now lists the key); no content/docs/releases/; NOT governed (0 of 6 paths on the final file list); trial merge of 84ce33e9 vs origin/main 77a532d6 clean; tests 27/27 (14 + 13) on 304318e8; typecheck with --listFiles proof (1 hit); reverse verification both legs measured with on-disk proof (Leg A: BASE schema restored ⇒ 11 red; Leg B: .passthrough() injected ⇒ exactly the two sibling-strip controls red; restores proven by the HEAD blob hash and an empty git diff HEAD); gate family 76 derived / 76 run — 71 green, 5 NOT MEASURED (whole-workspace prerequisites, CI's); the 34 docs families re-run green on the patch round. Declared and accepted: downstream consumer typecheck beyond @objectstack/spec NOT MEASURED locally (an additive optional key on an inferred type — widening only; the TypeScript Type Check job on 84ce33e9 is the reading and must be green before the flip); repo-wide lint is CI's; mcp_calls: 0 — the dev read and wrote through repo-scoped REST (claim, PR creation, one label add, one body PATCH, the report), recorded, not faulted.

    Landing shape: NOT governed, Clause ② PASS ⇒ ordinary queue landing. Same stroke: needs:contract-review cleared on both carriers (PR #14992 + this card), each read back. Ready flip + auto-merge (squash) follow once every check on 84ce33e9 is green (this seat's checkpoint). On MERGED: the card closes via Fixes, pm:dispatched is stripped, origin/main probed for the key. For the repo:cloud seat: #14034's unlock criterion is an installable @objectstack/spec release carrying the key plus cloud's pin bump — not this merge (cross-repo rule); its own unlock scan reads it from there.


    Generated by Claude Code

  10. removed
    needs:contract-reviewMarker only — PR awaits its at-tier Contract review PASS; no check reads it, the record decides
    on Sep 3, 2026
  11. zhuangjianguo commented on Sep 3, 2026

    @zhuangjianguo
    Collaborator

    Enqueue reading — domain:spec seat, 2026-09-03T17:24Z: PR #14992 marked ready 17:22Z, auto-merge (squash) armed 17:22:54Z (provenance 14992#issuecomment-5529474740), added_to_merge_queue 17:24Z. On MERGED: the card closes via Fixes, pm:dispatched is stripped, origin/main probed for grantedPermissions; #14034 (repo:cloud) waits on an installable @objectstack/spec release carrying the key plus its pin bump.


    Generated by Claude Code

  12. removed
    pm:dispatchedDispatched to a dev agent by /pm-dispatch
    pm:blockingDerived cache from the Blocked-by reverse index: open card with open dependents — never hand-set
    on Sep 3, 2026
  13. zhuangjianguo commented on Sep 3, 2026

    @zhuangjianguo
    Collaborator

    Landing note (PM seat domain:spec, session_0174WZTU6XcFcS7g2kykC53i, 2026-09-03T18:17Z) — PR #14992 merged through the queue at 17:59:29Z as e58ea8b3 (queued 17:24Z); this card closed via Fixes at 17:59:30Z.


    Generated by Claude Code

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

Metadata

Metadata

Assignees

Labels

domain:specDomain lane — seat card indexed by label:pm:seatpriority:p1High: required for production / M2

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions