Skip to content

access model: cite real access-control systems in extdeps, derive std, enforce via lens - #5415

Merged
briansrls merged 5 commits into
mainfrom
access-model-step3
Jun 21, 2026
Merged

briansrls merged 5 commits into
mainfrom
access-model-step3

Conversation

@briansrls

Copy link
Copy Markdown
Contributor

First substantive step of dismantling ctrl: model code visibility / access as a real, derived
fact instead of a git-topology nickname. Supersedes #5414 (closed), which silently re-coined
Zanzibar as a std abstraction — the §3 violation this PR corrects.

Method (DESIGN §3): model real systems → derive std → validate

Rather than invent an access abstraction from one system, model several real access-control systems
faithfully in extdeps/access/ (cited, real names — as extdeps/cloud/gcp/iam + extdeps/github
already do), then derive the std abstraction from their commonality and validate it by mapping
each system in. If a real system can't be expressed, the abstraction is wrong.

dsl/extdeps/access/ — four systems, cited, compile-verified

File System Citation
zanzibar.dag ReBAC — relation tuples + userset-rewrite USENIX ATC '19
aws_iam.dag policy/ABAC — Effect/Principal/Action/Resource/Condition IAM policy v2012-10-17
posix.dag DAC — owner/group/other rwx + setuid/setgid/sticky POSIX.1-2017
rbac.dag role-based — UA/PA/RH, PRMS=OPS×OBS NIST/ANSI INCITS 359-2004

dsl/std/access.dag — the DERIVED abstraction

AccessRequest<S,A,O>, AccessDecision = Permit | Deny (combined Deny-wins — the fail-closed meet),
AccessPolicy<S,A,O> (DESIGN §4 Realization: one interface, N system handlers).

dsl/std/access_validation_test.dag + dsl/test/claim/access_iam_validation_test.dag — validation

POSIX (DAC), RBAC, and AWS IAM (ABAC) each fully inhabit the same AccessPolicy<S,A,O>.
11 decision witnesses, green by execution (gunbc run --claim-run).

The headline finding: ABAC "context" decomposes — no new dimension

The validation first flagged a strain: AWS IAM Conditions seem to decide on request context beyond
subject/action/object. Re-derivation shows "context" was itself a nickname — it decomposes:

  • condition keys that are really attribute-carrying S/A/O (s3:prefix → an action parameter; tags → subject/object attributes), and
  • an environmental remainder grounded in frameworks already in the repo (aws:SourceIp internal-CIDR → the request's NetworkLocality; aws:CurrentTime → Timestamp).

So std/access is structurally unchanged — the generics carry ABAC once instantiated richly.
access_iam_validation_test.dag proves it: an "Allow s3:GetObject IF internal-network AND prefix
matches AND MFA present"
statement decided entirely from grounded fields, with no context map.

src/v2/lens/{visibility,visibility_test}.dag — enforcement (Step 3, pure core)

A fail-closed lens flagging the §5 leak the publication step must never commit: a public module
importing a non-public one
. 4 lens-unit witnesses green by execution; breaking the scanner turns the
leak witness red. (Live-tree wiring — facts from the resolved program via a Rust seed / dependency_lens
— is the next step, marked in the file.)

Verification (DESIGN §5 — green by execution, not just typecheck)

  • Full dsl compile 0 diagnostics; perturbation of each new file produces a located error.
  • 15 test fn witnesses green by execution across the access validation + the lens.
  • Discriminating reds confirmed for the leak rule, POSIX class-resolution, and the SourceIp→NetworkLocality decomposition.

Deferred (tracked)

  • Validate Zanzibar into std.access (the ReBAC action/grouping-fusion mapping).
  • Rework the visibility lens to consume std.access and walk the real import DAG (Rust-seed bridge).
  • Ownership invariants (one-owner / inheritance / DAC) as additional lens scanners.

🤖 Generated with Claude Code

briansrls and others added 4 commits June 20, 2026 22:05
…zibar)

Separate the facts conflated behind the "repo split" (repo == privacy) into independent
concepts (DESIGN §2/§3): ownership and visibility as relations over real .dag nodes,
anchored on Google Zanzibar / ReBAC. A relation tuple is structurally a substrate Edge,
so the access graph is a subgraph of the node graph — not a side-store. Composes with
SecretRef (orthogonal): SecretRef keeps secret values out of git; this keeps private
code/data out of the public projection.

v2.std.access:
- Relation (Owner|Reader|Writer|Member), Subject (Everyone | Is | MembersOf userset),
  Grant (= a labeled Edge).
- Ownership: OwnerResolution (Owned|Unowned, not Option-overloaded); owner_of /
  effective_owner (unowned => operator, fail-closed) / is_owner.
- Visibility: can_read / can_write (relation hierarchy Owner >= Writer >= Reader),
  subject_admits (public / direct / one-level org membership), is_public, and the
  leak rule effective_public (public AND every exposed dependency public — fail-closed
  meet-fold; a public node importing a private one cannot be published).

v2.std.access_test: principals (operator, gunb_ai_org, alice, bob) declared as real
.dag nodes, referenced by node identity (Symbol) — no minted id. 17 executed witnesses.

Verified (DESIGN §5): compile 0-diagnostics + discriminating red; 17/17 test fn
witnesses green by execution (gunbc run --claim-run); breaking the leak rule's
dependency meet-fold turns its witness red.

Deferred to the enforcement lens (follow-up): one-owner / inheritance / DAC invariants,
recursive userset resolution, and walking effective_public over the real import DAG.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…h (pure core)

A lens (pure reader, fail-closed) that flags the §5 leak the publication realization must
never commit: a PUBLIC module that imports a module which is not public. This is the
import-graph realization of access.dag's leak rule (effective_public).

src/v2/lens/visibility.dag:
- ModuleVisibilityFact { module: QualifiedName, public: Bool, imports: List<QualifiedName> }
  (keyed on the `module` identity, not a filepath), VisibilityLeak { importer, imported }.
- module_is_public (fail-closed: a module absent from the set is non-public, so an unknown
  import is a leak), scan_visibility_leaks (enumerates located leaks), visibility_clean.

src/v2/lens/visibility_test.dag: 4 lens-unit witnesses — leak caught, clean closure passes,
fail-closed unknown import, private-importer exempt.

Verified (DESIGN §5): compile 0-diagnostics; 4/4 witnesses green by execution
(gunbc run --claim-run); breaking the scanner turns the leak witness red.

Deferred (decision point): the LIVE corpus witness ("0 leaks in the real tree") needs live
ModuleVisibilityFacts from the resolved program — public via access.is_public over grants,
imports via v2.std.dependency.dependency_lens (kind ModuleDependsOn). That bridge is a Rust
seed / corpus-as-node handle (cf. the extdeps_shape_transport_policy lens). Also not yet
enforced: the ownership invariants (one-owner / inheritance / DAC).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…validate

Corrects the §3 nickname violation: the earlier src/v2/std/access.dag silently re-coined
Zanzibar as a std abstraction. Method (per discussion): model several real access-control
systems faithfully in extdeps/access (cited, real names), then DERIVE the std abstraction
from their commonality and validate it against each ("see if anything breaks").

extdeps/access/ (cited, real names; each compile-verified):
- zanzibar.dag  — Google Zanzibar relation tuples + userset-rewrite config (USENIX ATC '19)
- aws_iam.dag   — AWS IAM JSON policy: Effect/Principal/Action/Resource/Condition (v2012-10-17)
- posix.dag     — POSIX.1-2017 file mode bits (owner/group/other rwx + setuid/setgid/sticky)
- rbac.dag      — NIST/ANSI INCITS 359-2004 Core+Hierarchical RBAC (UA/PA/RH, PRMS=OPS×OBS)
  (alongside the pre-existing extdeps/cloud/gcp/iam + extdeps/github as further data points)

std/access.dag — the DERIVED abstraction: AccessRequest<S,A,O>, AccessDecision = Permit|Deny
(combined Deny-wins), AccessPolicy<S,A,O> (DESIGN §4 Realization: one interface, N handlers).

std/access_validation_test.dag — POSIX (DAC) and RBAC both fully inhabit the abstraction;
7 decision witnesses green by execution; broken POSIX class-resolution turns a witness red.

Findings (what strains the shape): POSIX's object-attached ACL inhabits AccessPolicy directly;
RBAC's global policy must be curried in; AWS IAM Conditions decide on request CONTEXT beyond
S/A/O (ABAC needs a context dimension); Zanzibar fuses action + subject-grouping into `relation`.

Removes the superseded src/v2/std/{access,access_test}.dag (PR #5414, closed). The Step-3
visibility lens is independent and unaffected.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…, std core unchanged

The earlier validation flagged a "strain": AWS IAM Conditions decide on request context
beyond subject/action/object. Re-derivation shows that "context" is itself a nickname — it
DECOMPOSES, and the std abstraction needs no new dimension:

  * Most condition keys are attribute-carrying S/A/O — modeled thin (bare ids), they LOOKED
    like a separate dimension. s3:prefix -> an ACTION parameter; principal/resource tags ->
    subject/object attributes.
  * The environmental remainder grounds in frameworks already in the repo: aws:SourceIp
    (internal CIDR) -> the request's NetworkLocality (Lan/Loopback/... vs Internet);
    aws:CurrentTime -> Timestamp. ("derived" = decomposed to grounded atoms; the live values
    remain runtime observations, but their TYPES are existing frameworks — nothing minted.)

So AccessRequest<S,A,O> / AccessPolicy<S,A,O> are UNCHANGED — the generics carry it once
instantiated richly. test/claim/access_iam_validation_test.dag proves it: an
"Allow s3:GetObject IF internal-network AND prefix matches AND MFA present" statement decided
ENTIRELY from grounded S/A fields, inhabiting the SAME AccessPolicy<S,A,O> as POSIX/RBAC, with
no context map. 4 decision witnesses green by execution; breaking the SourceIp->NetworkLocality
decomposition turns the external-source witness red.

std/access.dag: derivation receipt updated (STRAIN -> RESOLVED); S/A/O noted as attribute-carrying.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…direction import)

The CI floor's layering witness (clean_tree_no_wrong_direction_imports_holds) went RED:
dsl/std/access_validation_test.dag was module std.test.access_validation (std layer) but
imports extdeps.access.posix/rbac — std must not import upward to extdeps.

Move it to dsl/test/claim/access_validation_test.dag (module test.claim.access_validation,
the test layer, which may import extdeps — the IAM validation test already does). No logic
change. dsl compiles 0-diagnostics.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@briansrls
briansrls merged commit 1e6ca46 into main Jun 21, 2026
1 check passed
@briansrls
briansrls deleted the access-model-step3 branch June 21, 2026 01:12
briansrls added a commit that referenced this pull request Jun 21, 2026
…key-completeness lens

#5418's external-authority gate scans the LIVE extdeps tree and fail-closes on any
module lacking an anchor. Once #5422 cleared the FreeMonoid collision and the missing
gate_is_heavy_resolve arm let the plan resolve, the gate correctly flagged 5 modules
that landed on main AFTER #5418 authored its roster and were neither anchored nor
backfilled:

  missing: extdeps.access.{aws_iam,posix,rbac,zanzibar}   (#5415 access model)
  missing: extdeps.cache.key_completeness                 (#5423 cache lens)

The 4 access modules already carry their real upstream citation in a `// Source:`
comment (AWS IAM grammar, POSIX.1-2017/opengroup, ANSI/NIST RBAC, the Zanzibar USENIX
paper) — promoted each to the structured `extdeps_external_authority_anchor` carrier
the gate gates on (DESIGN §3: the mark on the carrier is the authority). No access
sibling is backfilled, so anchoring (not backfilling) is the consistent treatment.

extdeps.cache.key_completeness is an internal §5 cache-soundness LENS (a pure reader
over the cache catalog), not a cited external-system model — so it joins its 8
extdeps.cache.* siblings on the backfill_pending roster as tracked debt, not a fake
external anchor.

Verified by execution: the floor's discovery-corpus (587 witnesses) and
extdeps_external_authority_gate_passes both go GREEN with this change (live
violation set empty, roster count > 150).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
briansrls added a commit that referenced this pull request Jun 21, 2026
…ive #5297) (#5418)

* extdeps: external-authority anchor carrier + fail-closed CI lens (revive #5297)

Land the never-merged #5297 mechanism onto current main: a structured,
lens-checkable external-authority citation on every dsl/extdeps module,
enforced fail-closed on the CI floor.

Carriers
- extdeps.uri: Uri { scheme: UriScheme, locator } with closed-sum
  UriScheme = Http | Https | File | Ftp (RFC 3986 / 7595 grounded).
- extdeps.external_authority: ExternalAuthority { uri }, FactAuthorityOverride,
  and the canonical `data extdeps_external_authority_anchor` row.

Lens (v2.lens.extdeps_external_authority)
- Fail-closed live policy per module: machinery-exempt -> backfill-pending ->
  else require a present external Http/Https anchor. Scheme decoded by exhaustive
  constructor identity (decode_uri_scheme), never a URL-prefix string.
- Violations: MissingFormalAnchor / UnrecognizedAnchorScheme / NonExternalAnchorScheme.

Host projection (extdeps_shape_transport_policy_project.rs)
- Structural read of the anchor record; live roster derived from the module-path
  index (declared module name, not directory); backfill + machinery-exempt facts.
- 9 builtins wired through 04_method.dag / v1_interpreter.rs / v1_compiler_infer_method.rs.

CI
- ExtdepsExternalAuthorityGate enrolled in gunbc_ci_spec and scheduled on the floor;
  runs uri witnesses + live-corpus clean-tree + RED perturb receipts.

44 extdeps modules carry external anchors; 125 remain in the shrinking
backfill_pending snapshot.

Reconciliation onto current main: the snapshot keys on declared module name, so
the post-#5391 directory reorganizations (rust/, package_managers/, ...) need no
rename. The only stale entry, extdeps.diagnostic.redfish, was dropped (redfish
moved to extdeps.bmc.redfish, separately covered).

Verified by execution: 18 projection unit tests; 14 floor-enrolled .dag witnesses
(GREEN clean-tree + machinery-exempt fold + 3 RED perturbs + live anchor-drop RED);
live-tree discrimination on a real cited module (Https->File and anchor-drop both
flip the gate RED, revert restores GREEN); ci_spec/ci_floor_plan enrollment
witnesses; gate main -> ExitSuccess via real shell; cargo fmt + clippy -D warnings.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019Mi3t2wX7UPzgqLyYAE1kS

* ci_floor_plan: add missing gate_is_heavy_resolve arm for ExtdepsExternalAuthorityGate

#5418 added ExtdepsExternalAuthorityGate to the Gate enum and to every match
over it EXCEPT gate_is_heavy_resolve (ci_floor_plan.dag:154), so once the v1↔v2
FreeMonoid collision cleared (#5422 now in this branch) the plan resolve reached
this fn and fail-closed on a non-exhaustive match:

  ci_floor_plan.dag:154:3: error: non-exhaustive match:
    missing variant(s) ExtdepsExternalAuthorityGate

The extdeps external-authority gate is a focused lens witness (filesystem_read
over dsl/extdeps/**), not a whole-tree heavy resolve like SourceRootIngest /
DslCompileClean — so it joins the other lens gates (Layering/ResolvedImports) at
`false`. Plan-data only; resolved at runtime by claim_executor, no stage0 seed.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* extdeps live-clean: anchor the 4 access modules + backfill the cache key-completeness lens

#5418's external-authority gate scans the LIVE extdeps tree and fail-closes on any
module lacking an anchor. Once #5422 cleared the FreeMonoid collision and the missing
gate_is_heavy_resolve arm let the plan resolve, the gate correctly flagged 5 modules
that landed on main AFTER #5418 authored its roster and were neither anchored nor
backfilled:

  missing: extdeps.access.{aws_iam,posix,rbac,zanzibar}   (#5415 access model)
  missing: extdeps.cache.key_completeness                 (#5423 cache lens)

The 4 access modules already carry their real upstream citation in a `// Source:`
comment (AWS IAM grammar, POSIX.1-2017/opengroup, ANSI/NIST RBAC, the Zanzibar USENIX
paper) — promoted each to the structured `extdeps_external_authority_anchor` carrier
the gate gates on (DESIGN §3: the mark on the carrier is the authority). No access
sibling is backfilled, so anchoring (not backfilling) is the consistent treatment.

extdeps.cache.key_completeness is an internal §5 cache-soundness LENS (a pure reader
over the cache catalog), not a cited external-system model — so it joins its 8
extdeps.cache.* siblings on the backfill_pending roster as tracked debt, not a fake
external anchor.

Verified by execution: the floor's discovery-corpus (587 witnesses) and
extdeps_external_authority_gate_passes both go GREEN with this change (live
violation set empty, roster count > 150).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant