Repository navigation
content(blog): how do you catch an AI's silent permission change before it ships? the access-matrix gate (slot 2026-10-23) - #282
Open
objectstack-fleet[bot] wants to merge 2 commits into
Conversation
… access-matrix snapshot gate on a 7-entry CRM New post for the Sat 2026-10-24 slot, in en, zh-Hans, zh-Hant (generated by pnpm gen:zh-hant), ja, ko, de, es and fr. date: 2026-10-24, topic governance, audience developer. Every transcript was produced on the published @objectstack/cli 17.7.0 against examples/app-crm at the tag @objectstack/cli@17.7.0, installed against the published packages: the allowDelete false -> true flip on crm_opportunity passes os validate with no finding (49 author-time rules, exit 0) and fails os compile and os build with "Access matrix drift (1 change)" and the line "'crm_sales_user' gains delete on 'crm_opportunity'" (exit 1); --update-access-matrix rewrites the snapshot and the git diff is the one changed line. A second measurement, modifyAllRecords true with allowDelete left false, yields two drift lines, which is the post's "effective capability, not raw flags" concept. Assets: cover-en.svg / cover.svg, two-commands-one-gate-en.svg / .svg, effective-not-raw-en.svg / .svg; the zh pair uses the Chinese set, every other locale the English set. No published post is edited; the review post's "No rule fires" sentence stays as it is. Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FeA1nwBz1ohH65dvffUGKr
…he CRM diff; cross-link the access-matrix gate post An en-only correction of how-to-review-ai-generated-app-changes, measured on the published @objectstack/cli 17.7.0 against examples/app-crm at the tag: the worked diff's allowDelete false -> true flip passes os validate (exit 0, no finding) but fails os build and os compile with "Access matrix drift (1 change)" / "'crm_sales_user' gains delete on 'crm_opportunity'" (exit 1), because the CRM example ships a committed access-matrix.json. The post said "os build is green"; it now says os validate is green, with a parenthetical on the CRM example, and Pass 2 gains one sentence after "No rule fires." naming the one gate that is not a rule, both linking the new post. updated: 2026-10-23T10:00:00+08:00 (the day the link goes live); date is unchanged. The rule counts (42 / 29) are deliberately untouched: they wait on the separate version-labelling decision. Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FeA1nwBz1ohH65dvffUGKr
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #270
Slot: Fri 2026-10-23, on the shared calendar (#267). Merge on the slot day only.
Seat review. The seat accepted the post as written. It also ruled option A on the review post, a minimal correction of a sentence that was false on a published page.
os buildwas never green on the CRM example's own diff: the access-matrix gate has refused it since 14.7.0. The post's rule counts are untouched; they are the subject of the separate pending decision #208. This PR and PR #281 both set the review post'supdated. At merge time the later slot wins. ACCEPT.Post for slot Fri 2026-10-23 (slate 1 of the daily cadence, #267; the seat moved it from Sat 2026-10-24 mid-task, daily publishing starting the day after this report). Staged on
lin/issue-270fromorigin/main3c5b6c6, two commits:7a18f59(the post) andca29ec3(the en-only correction of the review post, below), tipca29ec3, worktree/home/user/www-issue-270. Not pushed by the dev; the seat pushes and merges on the slot day only.What this PR adds
New post
content/blog/catch-ai-silent-permission-change-access-matrix-gate/in en, zh-Hans, zh-Hant (generated bypnpm gen:zh-hant), ja, ko, de, es and fr.date: 2026-10-23, noupdated,topic: governance,audience: developer. Title as the card's working title: How Do You Catch an AI's Silent Permission Change Before It Ships? The Access-Matrix Snapshot Gate, on a 7-Entry CRM.examples/app-crmat the tag@objectstack/cli@17.7.0, installed against the published 17.7.0 packages (not the monorepo),npx os --version=@objectstack/cli/17.7.0 linux-x64 node-v22.22.2. TheallowDelete: false -> trueflip oncrm_opportunitypassesnpx os validate(49 author-time rules,Validation passed (684ms), exit 0, zero mentions ofallowDelete,crm_sales_useror a matrix) and failsnpx os compilewith✗ Access matrix drift (1 change) — capability changes must be reviewed/• 'crm_sales_user' gains delete on 'crm_opportunity', exit 1.npx os buildfails identically (Build extends Compile).--jsongives{"success":false,"error":"access matrix drift","changes":["'crm_sales_user' gains delete on 'crm_opportunity'"]}.--update-access-matrixrewrites the snapshot;git diff -U8 access-matrix.jsonis the one-line"delete": false -> truehunk; the next compile passes.modifyAllRecords: trueadded withallowDelete: falseleft in place yieldsAccess matrix drift (2 changes):gains deleteandgains Modify All Dataoncrm_opportunity; validate still passes (575ms).access-matrix.json; compile/build only, never validate or lint; visible-not-impossible, an agent can regenerate the snapshot itself; scope is permission set x object; the steelman that a one-word flip is visible in the source diff anyway) plus the practical note that the flag rewrites the whole file and that the default three-line diff context cuts the row'spermissionSet/objectlines off.os validate; never says validate catches it.T4_STANDALONE) verbatim once per locale fromsrc/lib/positioning.ts. E1 is not used (it names editions; the house bar says no edition naming while [Decision] Pricing headline: www shows $20 / $45 per AI seat "billed annually (about two months free)", docs License & Pricing shows $24 / $54 per month with no annual price — which is the source, and is the annual discount real? #214/[Decision] Where does "Get started" go? Neither site has an ObjectOS Cloud signup URL, so every plan CTA loops www → docs → www #215 are pending). No pricing, no signup URL, no token figures, no competitor claims.npm create objectstack@latest, thennpx os compile --update-access-matrixas the first commit andnpx os compilein CI.how-to-review-ai-generated-app-changes(en only, so every locale links/en/),objectos-agent-permission-boundaries(zh-Hans/zh-Hant link their own locale), glossaryreviewable-diffanddeclared-vs-enforced(zh links own locale, others/en/),how-to-point-your-agent-at-objectstack.cover-en.svg/cover.svg(1200x675),two-commands-one-gate-en.svg/.svg(1200x640, the validate-vs-compile mechanism and the human loop),effective-not-raw-en.svg/.svg(1200x560, the fold and the two-line measurement). The zh pair uses the Chinese set, every other locale the English set. Each diagram adds what the cover lacks.One published post is edited, on the seat's ruling (option A): see "Correction to a published post" below.
Claims and their sources
@objectstack/cli@17.7.0=4e4e881, unless noted)content/docs/permissions/access-matrix.mdx:7-12(identical atorigin/main, diff empty)access-matrix.jsonnext toobjectstack.config.ts;os compile --update-access-matrix; "Noaccess-matrix.json→ the gate is skipped (not opted in). Bothexamples/app-crmandexamples/app-showcaseare opted in."access-matrix.mdx:16-24; both files present at the tag (git cat-file -e)access-matrix.mdx:36, 39-43; printed verbatim by the run (proof/04-compile-modified.log)access-matrix.mdx:45-72access-matrix.mdx:74-85;content/docs/permissions/explain.mdx:106-108("the same evaluation run over representative (permission set × object) pairs; explain is the per-decision zoom lens")packages/cli/src/commands/compile.ts:849-895Build extends Compilepackages/cli/src/commands/build.ts:3-6; measuredproof/09-build-modified.logexit 1packages/lint/src/build-access-matrix.ts:104-129build-access-matrix.ts:47-68, 77-81packages/spec/src/security/permission.zod.ts:179-224permission.zod.ts:185-186crm_sales_userrow"delete": false,guest_portalinsert-only, OWD valuesexamples/app-crm/access-matrix.json(82 lines, 7 entries)crm_opportunityrow before/afterexamples/app-crm/src/security/sales-positions.ts:39-57(the card cites:50, which isorigin/mainnumbering)git ls-tree -r @objectstack/cli@17.7.0 -- packages/create-objectstackhas noaccess-matrix.json; the #230 writer's real 17.7.0 scaffold has nonecontent/blog/how-to-review-ai-generated-app-changes/index.mdx:112; measured true of validate (proof/03-validate-modified.log, 0 matches for allowDelete/crm_sales_user/access-matrix)text/json/diffcode blockscratchpad/issue-270/proof/00-14*(trimmed, never rewritten)src/lib/positioning.ts(T4_STANDALONEand each locale'st4)The run, in order
Scratchpad
issue-270/app/crm:git archive @objectstack/cli@17.7.0 examples/app-crm,workspace:*deps rewritten to17.7.0(runtime, service-i18n, spec, cli, driver-sql, formula, objectql), the monorepo-onlypathsrule and vitest removed,pnpm install(registry reachable), committed as0b5e971sogit diffworks.npx os compilebaseline:→ Checking access-matrix snapshot (ADR-0090 D6)...then✓ Build complete (667ms), exit 0 (gate present, auto-pass).allowDelete: false -> true.npx os validate:Running author-time rules (49),✓ Validation passed (684ms), exit 0.npx os compile:✗ Access matrix drift (1 change)/• 'crm_sales_user' gains delete on 'crm_opportunity'/If intended, re-run with --update-access-matrix..., exit 1.--json:success:false,error:"access matrix drift", onechangesentry.npx os compile --update-access-matrix:Access matrix snapshot written to access-matrix.json (ADR-0090 D6) — review the diff., exit 0.git diff access-matrix.json: the one hunk at@@ -51,7 +51,7 @@;-U8shows the row with itspermissionSet/objectkeys.npx os compileagain:✓ Build complete (508ms).npx os buildon the flipped source: same drift line, exit 1.modifyAllRecords: trueadded on the same row:npx os compile(2 changes)gains delete+gains Modify All Data, exit 1;npx os validate✓ Validation passed (575ms). Restored (git diff HEADempty).Measured vs the card
'crm_sales_user' gains access to 'crm_opportunity' (delete), is the new-entry form (build-access-matrix.ts:113). A bit flip on an existing row prints'crm_sales_user' gains delete on 'crm_opportunity'(:118). The post quotes the measured line; the cover and both diagrams carry it verbatim.needs_decisionon the topic.Gates, on
ca29ec3(scripts/pm/os-verify-lock.sh, slotwww-270,VERDICT command-exit 0, held 84 s, waited 5 s, shared-box seconds; logsscratchpad/issue-270/gates5/*.logwith.exitfiles; the identical chain was also green on7a18f59(gates4, the post alone) and on the earlier 2026-10-24-dated tip4ebe660(gates3))pnpm content:lint✓ content lint passed (355 files, 46 glossary terms checked)pnpm content:lint --publishedpnpm checkResult (160 files): 0 errors, 0 warnings, 0 hintspnpm build✓ zh-Hant: generated 48, kept 0 hand-maintained, 8 with body links pointed at /zh-Hant/·svg-text-fit: OK (124 SVGs, 2671 text boxes, 185 filtered elements, tolerance 2px, 1 allowlisted)·[build] 920 page(s) built in 48.81s·✓ content lint --dist passed (494 built blog pages checked)· CJK line breaks, quotation marks (12811 curly marks, 0 wrong) and links (99459) passed ·git statusclean afterwardspnpm seo:smokeSEO smoke test passed (910 HTML pages checked; 594 sitemap URLs against 316 noindexed pages; 5679 canonical/hreflang annotations; 17397 prose blocks language-checked; 99449 internal links redirect-checked)pnpm svg:lint(the six new files)svg-text-fit: OK (6 SVGs, 198 text boxes, 2 filtered elements, tolerance 2px, 0 allowlisted); tightest box 6.4 pxAdvisories for this post, all expected:
Date is in the future: Fri Oct 23 2026(x8);Title is N columns wide ... search results show about 60(en 116, zh 86, ja 112, ko 108, de 160, es 175, fr 183: the ruled title);Description is N columns wide ... about 160(en 228, zh 193, ja 245, ko 241, de 226, es 227, fr 229; every description is at or under the 230-character ceiling, after shortening de/es/fr). For the review post:Updated is in the future: 2026-10-23.An earlier run on the same content (gates1) failed
seo:smokewith 7 issues: the English doc quote rendered underlang="ja"etc. was judged not in its declared language. Fixed by wrapping that one blockquote in the seven translations in ablockquotecarryinglang="en"(the smoke test's own remedy); the gate chain was re-run green.Browser check (final build,
scratchpad/issue-270/shots.mjs, resultsscratchpad/www/shots-270/results.json, 110 files)dist/of7a18f59served byastro previewon 127.0.0.1:49123, started and stopped by PID (confirmed gone, port closed). Playwright at/opt/node22/lib/node_modules/playwright,executablePath /opt/pw-browsers/chromium, no install.colorSchemeplusdata-theme), lazy images scrolled into view first, fold and full-page shots: 48 of 48 pages HTTP 200,scrollWidth == clientWidtheverywhere, no element past the viewport outside a scroll container, cover plus two body images on every article load with natural width, no repeated image, no page errors, both lists link the post (it is the first chronological card, ahead of the 2026-10-15 post), notime.updatedrendered, JSON-LDdatePublished == dateModified == 2026-10-23, rendereddatetime="2026-10-23".content-type image/svg+xml, every text node's computed fill against the fill under it, lowest 4.59:1 (amber caps on cream), covers 5.06:1, 0 text nodes on a dark fill, 0 without a fill. The first run measured 4.13-4.43:1 for the small grey (#657872, the house value) on the cream and blush fills; the grey was darkened to#4b5d56(6.17-7.00:1 on every fill used) before the final commit.post-en-1440x750-light-fold,post-en-1920x945-light-fold,post-en-390x844-dark-fold,post-en-1440x750-dark-full,post-zh-Hans-1440x750-dark-fold,list-en-1440x750-light-fold,list-zh-Hans-360x800-dark-fold, and the native renders ofcover-en(light),cover(dark), bothtwo-commands-one-gate(light), botheffective-not-raw(light).Correction to a published post (seat ruling: option A, 2026-10-17)
content/blog/how-to-review-ai-generated-app-changes/index.mdx, en only (the post has no other locale), commitca29ec3, 3 lines changed.dateunchanged;updated: 2026-10-15T10:00:00+08:00becomesupdated: 2026-10-23T10:00:00+08:00, the day the link goes live. The rule counts ("42 rules", "29 of them can emiterror") are deliberately untouched: they wait on the separate version-labelling decision (#208).Line 82, before:
Line 82, after:
Line 112 (Pass 2), before:
Line 112, after:
Evidence that the old sentence was wrong (
scratchpad/issue-270/proof/09-build-modified.log): the review post's own diff, applied toexamples/app-crmat the tag@objectstack/cli@17.7.0with the committedaccess-matrix.jsonin place, run with the published CLI:and
npx os validateon the same tree:✓ Validation passed (684ms), exit 0 (proof/03-validate-modified.log). The claim was also false when published:examples/app-crm/access-matrix.jsonexists from at least@objectstack/cli@14.7.0(2026-07-13) and the drift check is incompile.tsat 14.7.0, 17.2.0 and 17.3.0 (2026-09-04, the last tag before the post's 2026-09-08date); the sentence entered in9ca8238(#153). "No rule fires" was and is true:validateprints no finding, and the snapshot gate is not a registry rule.Browser check of the corrected post (
scratchpad/issue-270/shots-review.mjs,dist/ofca29ec3on 127.0.0.1:49124, stopped by PID, port closed; shotsscratchpad/www/shots-270/review-*): at 1440x750 and 390x844, light and dark, HTTP 200,time.updatedrenders "Updated Oct 23, 2026" withdatetime="2026-10-23", both links to the new post render with non-zero boxes (link colourrgb(61, 88, 240)light /rgb(145, 163, 255)dark), no horizontal overflow, JSON-LDdatePublished 2026-09-08T02:00:00.000Z,dateModified 2026-10-23T02:00:00.000Z. Looked atreview-1440x750-light-fold,review-1440x750-dark-correctionandreview-390x844-light-correction.Merge note from the seat: PR #281 (#271, slot 10-24) also back-links this post and sets its
updatedto 10-24; the seat resolves that conflict at merge time, later date wins.Deviations
sales-positions.ts:50; at the tag the line is 45 and the post says 45.blockquotewithlang="en"(first use of that pattern incontent/blog), with the translation in parentheses after it; without itseo:smokefails.#4b5d56rather than the house#657872, after the native-size check measured the house value at 4.13-4.43:1 on the cream/blush fills used here.datechanged in all seven sources, zh-Hant regenerated, commit amended, every gate and the browser check re-run on the new tip. The onlyupdatedstamped is the review post's, 2026-10-23, per the seat's option-A ruling.gh apiread of the card. No MCP calls, no REST writes, nothing posted.Generated by Claude Code
🤖 Generated with Claude Code
https://claude.ai/code/session_01FeA1nwBz1ohH65dvffUGKr