Repository navigation
feat(plugin-security): under a walled posture, deployment-level acceptance of package defaults is host configuration (acceptedDefaultPermissionSets, ADR-0131 D5) - #23884
Conversation
📓 Docs Drift CheckThis PR changes 1 package(s): 6 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:
⛔ 1 release-owned page(s) also name something this change touched. These are read-only:
What this run could not see
Coarse fallback — 15 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): Which tree this was computed onThis run read A worktree cut from an older # while this PR is open — GitHub drops the merge commit once it closes
git fetch origin 5372a4a3360de8b84c015f4b253c73db04b95f0a && git checkout 5372a4a3360de8b84c015f4b253c73db04b95f0a
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 94114c1f269f6b3d885f24b6f4689dce29504574 70b3cb0927cd817a09c947928dc33ed222e28e8e && git checkout -B drift-repro 94114c1f269f6b3d885f24b6f4689dce29504574 && git merge --no-ff 70b3cb0927cd817a09c947928dc33ed222e28e8e
node scripts/docs-audit/affected-docs.mjs --json 94114c1f269f6b3d885f24b6f4689dce29504574
|
Contract reviewServed-tier: Inputs: card #23385 (body and all seven comments, the ruling as triage quoted and verified it in 6205580248), this PR's body, file list and net diff against ① Derived judgments
② Semver level
③ Boundary flagsDev deviations (os-dev-report 6214518188):
Open questions, each answered:
Out-of-scope observation (one organization and no Added by this review:
Implemented-by: VERDICT: PASS Generated by Claude Code |
席位核对 · ACCEPT · PR #23884 @
|
Part of #23385
Clause-②: yes
The code half of #23385. Under a walled posture, a host can declare the deployment-level acceptance of package defaults as configuration:
SecurityPluginOptions.acceptedDefaultPermissionSets, under ADR-0131 D5. The walled refusal now names that option instead of "create the Default Organization". The card's third item, the ADR-0131 amendment line, is #23883 (Tier H, the maintainer lands it). #23385 remains open until that PR lands, and this PR does not close it.Authority: objectstack-ai/cloud#2872 ruling A′ (
6160165869, maintainer 「同意」), item 3, as triage quoted and verified it in6205580248.What changed
packages/plugins/plugin-security:SecurityPluginOptions.acceptedDefaultPermissionSets?: readonly string[]is new. It is read only under a walled posture, using the module's existingpostureEnforcesWallpredicate on the posture the plugin already resolves instart(). There is no second reading of the posture. When it is declared, possibly as[], three things follow:readAcceptedEveryoneSetsderiveseveryonefrom the declared names. Each name goes through the same per-set checks an accepted row goes through: the catalog serves it under a package, it declaresisDefault: true, anddescribeAnchorForbiddenBitspasses (ADR-0090 D7). A name that fails is withheld with its reason, aterrorfor a forbidden bit, as for a row.syncAudienceBindingSuggestionsreads and writes nothing, so the boot has no throw and the two(non-fatal)warn sites stay silent.reconcileAudienceBindingSuggestionsstill counts existing rows once, now as "not read" and naming the option.409 SUGGESTION_STATEnaming the option. The deployment authority still answers first, so a non-operator gets 403.acceptedDefaultPermissionSets…".single: unchanged, includingNO_RECORD_OWNER_MESSAGE's text. Every pre-existing call passes noDeploymentAcceptance, which reads as the record.TypeError). A declaration undersingleis refused atstart(), the first moment the posture is known.singlewould never read it, so it would only look enforced.deployment?: DeploymentAcceptanceonreadAcceptedEveryoneSets,syncAudienceBindingSuggestionsandreconcileAudienceBindingSuggestions;SuggestionDeps.acceptedDefaultPermissionSets?; theDeploymentAcceptancetype; andWithheldAcceptance.source.minorfor@objectstack/plugin-security,Clause-②: yes (widening).Not touched: no NULL-
organization_idrow, no new table, nopackages/specfile, notbuiltin-positions.ts.Which cases read the declared list: measured, then decided
resolveDefaultOrganizationIdreturns thedefaultslug, else the only organization, else nothing. Today, on a walled deployment:defaultslug existsdefaultdefaultCall: a declaration under a wall is the only source, whatever the organization count. The four-axis reason:
6159344518: 48 and 12 organizations, nodefault). Reading the list only in row 3 would leave a host's declaration ignored the day adefault-slug organization appears.single) or a malformed one is refused at boot or construction, never skipped.Tests (at
70b3cb0927)pnpm --filter @objectstack/plugin-security exec vitest run --maxWorkers=2over the whole package: 187 files passed, 3725 tests passed, 45 skipped.pnpm --filter @objectstack/plugin-security typecheck: exit 0.[ADR-0131 D5] the walled posture …block ofsuggested-audience-bindings.test.ts(8 tests) and inwalled-accepted-defaults.boot.test.ts(3 tests, booting a realObjectKernel):default, declared[]: the reconcile resolves (no throw, so the plugin's warn sites log nothing), the captured warns are[],everyoneis the baseline, and accept, dismiss and list answerSUGGESTION_STATE/409 with a message whose first sentence is pinned and which names the option;everyone(absent before), with no row written;forbidden: trueand never granted; an unknown, non-isDefaultor package-less name is withheld with its reason;defaultorganization holds an accepted row: that row is not read;single, both undeclared and with a declaration passed to the module: the message keeps "Remedy: create the Default Organization (slug 'default')";single-posture boot refusal with its controls (singleundeclared boots; walled declared boots).single-posture suggestion tests already in the file are byte-for-byte unchanged and green. The new block is appended.Ablation (
node scripts/ablation-replace.mjs; the tests importsrc/relatively, so nodist/leg; both restores verified blob == HEAD andgit diff HEADempty):hostDeclaredAcceptancereturningundefined(the option's read removed): 5 red. They are declared[](no warn, everyone = baseline, the door refusal); the declared set granted; the forbidden set withheld; unserved names withheld; and the record not read when declared. The other 47 stayed green. That includes the undeclared-walled message, thesinglepins and the shape refusals, which do not depend on the read.noRecordOwnerMessagealways returningNO_RECORD_OWNER_MESSAGE: 1 red, the walled-undeclared message pin.Gates (local, at
70b3cb0927)node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstackderived 67 families from the 6 changed paths (625 changed lines). All 67 ran, with exit codes captured before any pipe, and all exited 0.--ranreconciliation: "67 derived famil(ies) accounted for — 67 run, 0 NOT-MEASURED (a DERIVED zero — all 67 recorded an exit code and none of them is 3)."On the first pass, three families exited 3 (prerequisite not met) and one hit my 300 s wrapper. Each was then re-run to a verdict:
check-plugin-teardown-shape --self-testneeded an unshallow clone;check:type-check-debtran to exit 0 under the lock in 114 s, and it built every package;check:dual-build-cjs-loadsandcheck:i18nexited 0 after that build.Acceptance notes
resolveTenancyPosture()(packages/types/src/env.ts:147) is the deployment's env reading, which plugin-auth'stenancyservice is built from. The security plugin already reads that service once instart()(this.tenancyPosture). This PR reads that resolved value throughpostureEnforcesWall(packages/spec/src/security/tenancy-posture.ts:53), the predicateassertDeploymentAuthorityin this module already uses. It does not callresolveTenancyPosture()again, which would be a second reading beside the service.isDefaultsets, or show prompts no door can act on.WithheldAcceptance.sourceis a new required field on an exported, output-only type. Only this package constructs the type.cloudadoption (dropping the boot-log warn check ruling A′ added to cloud#2709) is cloud's, at a later pin.🤖 Generated with Claude Code
https://claude.ai/code/session_015jUkR2UVa8FFGk3rWDX49C
Generated by Claude Code