Skip to content

feat(operad+kernel): §M12 admin-capability gate fills AdminScopeRewrite#31

Merged
MSD21091969 merged 2 commits into
masterfrom
round11/pr4-m12-admin-cap
Apr 21, 2026
Merged

feat(operad+kernel): §M12 admin-capability gate fills AdminScopeRewrite#31
MSD21091969 merged 2 commits into
masterfrom
round11/pr4-m12-admin-cap

Conversation

@MSD21091969
Copy link
Copy Markdown
Collaborator

Summary

Fills in the PR 3 dormant AdminScopeRewrite hook with the real §M12 classifier. Per doctrine in kb/research/kernel/20260417-t187-kernel-proper.md §M12 and Guido's scope confirmation on ffs0#33.

Classifier signature — method form

func (r *Registry) AdminScopeRewrite(env, state) bool

Moved from package-level function to *Registry method so case 2 (kernel-authority property MUTATE) can consult the type spec for authority_scope on additive MUTATEs (field not yet on node). Nil-receiver returns false — matches existing ValidateLINK / checkLiveness nil-safety pattern.

Admin scope

Case 1 — ontology-governed node types

ADD or MUTATE on any of:

  • system_instruction — S4 context overlay, shapes downstream reads
  • gate — fail-closed flow primitive (§M8); wrong gates brick Apply
  • twin_link — kernel-replication pairing (§M9)
  • transport_binding — wire-protocol declaration
  • kernel — creates a new sovereign substrate (Guido flag: include from day one)

Case 2 — kernel-authority property on non-kernel node

MUTATE of a property declared authority_scope: "kernel" on a target node whose type_id != "kernel". Kernel-typed nodes excluded here because they're already admin-scope via case 1 (ontology-governed-type rule).

Case 3 — ontology file

Deferred until §M16 (programmatic ontology publication). No HG footprint today.

Not in scope

LINK / UNLINK are not admin-scope in v1. Node-level ops only.

Precedence

The §M11 allowlist (SystemInternalEnvelope) runs first in checkLiveness. Kernel-URN actors, sweep WF13, and infrastructure ADDs bypass §M12 by design — the kernel is authoritative over its own substrate.

Capability walk uses existing operad.CheckAdminCapability — unchanged from pre-round-11.

Replay-safe by construction

fold.Replay doesn't call checkLiveness. Pre-PR-4 admin-scope rewrites persisted before this PR replay cleanly. Same prospective-only discipline as PR 1 and PR 3. Test pins it.

Tests (17 new)

9 operad-level (session_context_test.go):

  • ADD of all 5 ontology-governed types → admin-scope
  • ADD of ordinary type (program) → not admin-scope
  • MUTATE on gate node (ontology-governed) → admin-scope
  • MUTATE on kernel-authority field (program.target_t) → admin-scope
  • MUTATE additive-lookup (field not on node, type-spec consulted) → admin-scope
  • MUTATE on owner-authority field → not admin-scope
  • MUTATE on kernel-typed node → admin-scope via case 1
  • LINK → not admin-scope
  • Nil-registry → false

8 kernel-integration (liveness_test.go) — exactly Guido's list:

  1. Non-admin actor + ontology-governed ADD → rejected with §M12 ✓
  2. Admin actor (WF02 superadmin) + ontology-governed ADD → accepted ✓
  3. Non-admin actor + ordinary ADD → passes §M12 ✓
  4. Non-admin actor + kernel-authority MUTATE → rejected with §M12 ✓
  5. Non-admin actor + owner-authority MUTATE → passes §M12 ✓
  6. Kernel-URN actor + ontology-governed ADD → allowlisted before §M12 ✓
  7. SeedIfAbsent bypass → passes (structural skip, same as §M11) ✓
  8. fold.Replay invariant — pre-PR-4 rewrites replay cleanly ✓
go build ./...  # clean
go test ./...   # all packages pass

Diff shape

internal/kernel/liveness.go              +3  (-3)    call-site method form
internal/kernel/liveness_test.go        +267 (new)   8 §M12 integration tests
internal/operad/session_context.go      +119 (-14)   AdminScopeRewrite method + helpers
internal/operad/session_context_test.go +219 (-16)   9 classifier tests

Stacking

Pure-additive to PR 3. No new runtime wiring; the checkLiveness call just updates from operad.AdminScopeRewrite(env, state) to rt.registry.AdminScopeRewrite(env, rt.state). No envelope field change. No new operad method surface beyond the receiver form.

Round-11 close path

When this merges:

  1. Rebuild + targeted restart of both kernels (§M12 activates alongside §M11)
  2. Running-state update with T=171 round-11 section
  3. Move dev/scripts/ops/round11-pr1-postmerge-compensating-batch.{json,md}dev/reference/research-archive/
  4. Update moos-rewrite-envelope skill canonical example to use agent:* actor (Guido flagged the skill still shows user:sam which will quietly fail post-§M11)
  5. v3.13 grammar_fragment proposals (optional) from the Wolfram/Sam exploration thread: workspace_surface, calendar-adjunction η/ε automation, external_op for API-key blockers

Context

🤖 Generated with Claude Code

Fills in the PR 3 dormant hook with the real §M12 classifier per
doctrine in kb/research/kernel/20260417-t187-kernel-proper.md §M12 and
Guido's scope confirmation on ffs0#33.

Classifier signature — now a Registry method:

  func (r *Registry) AdminScopeRewrite(env, state) bool

Moved from package-level to method so case 2 (kernel-authority property
MUTATE on non-kernel node) can consult the type spec for authority_scope
lookup on additive MUTATEs where the field is not yet on the node. The
nil-receiver path returns false, matching the ValidateLINK / checkLiveness
short-circuit pattern.

Admin scope per Guido's answer:

  Case 1 — ADD of ontology-governed node type. The set is:
             system_instruction, gate, twin_link, transport_binding, kernel
           Any change to the grammar of the graph itself is superadmin.
           (Guido's flag on the M11/M12 plan: include `kernel` in the
           ontology-governed list so kernel-ADDs are admin-gated from
           day one.)

  Case 1 also covers MUTATE on a node of an ontology-governed type.

  Case 2 — MUTATE of a property with authority_scope=kernel on a
           NON-kernel target node. The ontology declares "only kernel
           may change this"; §M12 extends that to "or a superadmin-
           capable actor". Kernel-typed nodes excluded — their
           kernel-authority fields are covered by case 1 via the
           ontology-governed-type rule.

  (1) ontology file — deferred. No HG footprint today; revisits at §M16.

LINK/UNLINK are not admin-scope in v1. Doctrinal scope is node-level ops.

Precedence:
  §M11 allowlist (SystemInternalEnvelope) runs FIRST in checkLiveness.
  Kernel-URN actors, sweep WF13, and infrastructure ADDs bypass §M12
  by design — the kernel is authoritative over its own substrate.

Capability walk uses existing operad.CheckAdminCapability — unchanged
from pre-round-11.

Tests (9 operad-level + 8 kernel-integration):

  operad.TestAdminScopeRewrite_*:
    - ADD of all 5 ontology-governed types admin-scope
    - ADD of ordinary type (program) not admin-scope
    - MUTATE on gate (ontology-governed) admin-scope
    - MUTATE on kernel-authority field (target_t on program) admin-scope
    - MUTATE additive-lookup: field not on node, type-spec consulted
    - MUTATE on owner-authority field not admin-scope
    - MUTATE on kernel-typed node admin-scope (via case 1)
    - LINK not admin-scope
    - Nil-registry returns false

  kernel.TestApply_M12_* (Guido's 8-case list):
    1. Non-admin actor + ontology-governed ADD rejected with §M12
    2. Admin actor (WF02 superadmin) + ontology-governed ADD accepted
    3. Non-admin actor + ordinary ADD passes §M12
    4. Non-admin actor + kernel-authority MUTATE rejected with §M12
    5. Non-admin actor + owner-authority MUTATE passes §M12
    6. Kernel-URN actor + ontology-governed ADD allowlisted before §M12
    7. SeedIfAbsent bypasses §M12 (structural skip, same as §M11)
    8. fold.Replay of pre-PR-4 admin-scope rewrites replays cleanly
       (prospective-only invariant — same as PR 1, PR 3)

go build ./...  # clean
go test ./...   # all packages pass

Stacking:
- Pure-additive on top of the PR 3 hook. No runtime wiring change; the
  checkLiveness call site updates from package-level AdminScopeRewrite
  to registry.AdminScopeRewrite. No new envelope field. No new operad
  method beyond the method-receiver form.
- Replay-safe by construction: fold.Replay does not call checkLiveness.

Closes the round-11 PR stack. Round-close work (running-state update,
compensating-batch archiving, v3.13 grammar_fragment proposals from the
Wolfram/Sam exploration thread, moos-rewrite-envelope skill update to
use agent-as-actor pattern) follows in a separate round-close commit.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings April 21, 2026 15:43
@github-project-automation github-project-automation Bot moved this to Todo in mo:os Apr 21, 2026
Copy link
Copy Markdown

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request implements the §M12 admin-capability gate, which restricts ADD and MUTATE operations on ontology-governed types and kernel-authority properties to superadmin-capable actors. The implementation introduces the AdminScopeRewrite classifier and integrates it into the kernel's liveness check, accompanied by extensive tests. Feedback identifies a security vulnerability where bundling ADD and MUTATE operations in a single batch could bypass the gate, a potential bypass if property authority metadata is missing, and redundant logic in the classifier's type checks.

Comment thread internal/kernel/liveness.go Outdated
// the actor must hold superadmin capability via WF02 governs. Fails
// closed on any missing hop. The §M11 allowlist above already let
// kernel-actor envelopes through — they never reach this path.
if rt.registry.AdminScopeRewrite(env, rt.state) {
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

security-high high

In ApplyProgram, checkLiveness is evaluated against the initial state of the batch. Because AdminScopeRewrite returns false if the target node is missing from the provided state (see internal/operad/session_context.go:295), a MUTATE operation on a node created within the same atomic batch will bypass the §M12 admin-capability gate. This allows non-admin actors to modify kernel-authority properties on newly created nodes by bundling the ADD and MUTATE operations in a single program. Given that operad.checkAuthority for the kernel scope is a no-op that relies on this gate for enforcement, this represents a significant security gap. The admin-scope check should be evaluated against the evolving working state during the execution phase of ApplyProgram to prevent this bypass.

Comment thread internal/operad/session_context.go Outdated
Comment on lines +331 to +333
if prop, ok := node.Properties[field]; ok {
return prop.AuthorityScope, true
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

security-high high

The current implementation trusts the AuthorityScope stored on the node's property in the state. However, if a node was created via an ADD operation that did not include or correctly populate the AuthorityScope metadata (which ValidateADD does not currently enforce), this check will return an empty string, bypassing the admin-scope gate for properties that should be restricted to the kernel scope. Since operad.checkAuthority for the kernel scope is a no-op that defers to this gate, this results in a complete bypass of the authority restriction. The function should fall back to the registry's type specification if the property's authority scope is missing or empty in the live state to ensure a "fail-closed" security posture.

Suggested change
if prop, ok := node.Properties[field]; ok {
return prop.AuthorityScope, true
}
if prop, ok := node.Properties[field]; ok && prop.AuthorityScope != "" {
return prop.AuthorityScope, true
}

Comment on lines +311 to +315
if scope, ok := authorityScopeForField(r, node, env.Field); ok {
if scope == "kernel" && node.TypeID != "kernel" {
return true
}
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The check node.TypeID != "kernel" is redundant here because any node with TypeID == "kernel" would have already triggered an early return at line 304 due to being included in the ontologyGovernedTypes map. Removing this check simplifies the logic without changing behavior.

Suggested change
if scope, ok := authorityScopeForField(r, node, env.Field); ok {
if scope == "kernel" && node.TypeID != "kernel" {
return true
}
}
if scope, ok := authorityScopeForField(r, node, env.Field); ok {
if scope == "kernel" {
return true
}
}

Copy link
Copy Markdown
Collaborator Author

@MSD21091969 MSD21091969 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Comment-review — treat as request-changes. Two SECURITY-HIGH findings from @gemini-code-assist are real bypasses, and I need to retract/qualify my earlier PR 30 stance that fed one of them.

Blocker 1 — ApplyProgram in-batch ADD+MUTATE bypass

Gemini's liveness.go:81 flag is correct, and it defeats §M12 end-to-end. Attack shape:

env[0]  ADD      target:foo            actor=agent:non-admin    (ordinary type → not admin-scope, accepted)
env[1]  MUTATE   target:foo.kernel-auth-field  actor=agent:non-admin
          ↓
        AdminScopeRewrite(env, initialState) consults initialState.Nodes["target:foo"]
          → MISSING (only exists in workingState post-env[0])
          → no authority scope found → returns false
          → §M12 gate doesn't fire
          → kernel-authority field MUTATE lands unchecked

The non-admin actor pulls off an end-run around §M12 by bundling the creation and the privileged mutation into one atomic program.

I need to retract/qualify my PR 30 stance here. In that review I argued "keep initial-state check, same-batch session creation is bootstrap-via-seed-only, protected by skipLiveness" — that was scoped to §M11 session context, which refers to the emitter's session (exists pre-batch by doctrine). It did NOT account for §M12's need to classify the target node's admin-scope, which does depend on nodes created inside the batch.

Different question, different answer. My PR 30 call stands for §M11; PR 31 needs workingState for §M12.

Recommended fix: move the AdminScopeRewrite check from the pre-lock preflight into the fold loop under the write-lock. §M11 session-context stays in preflight (initial-state, per PR 30).

Sketch:

func (rt *Runtime) ApplyProgram(envelopes []graph.Envelope) ([]graph.EvalResult, error) {
    // §M11 preflight (unchanged) — session context against initial state
    rt.mu.RLock()
    for _, env := range envelopes {
        if err := rt.checkLivenessM11Only(env); err != nil { ... }  // renamed
        if err := rt.validate(env); err != nil { ... }
    }
    rt.mu.RUnlock()

    rt.mu.Lock()
    defer rt.mu.Unlock()

    workingState := rt.state
    for _, env := range envelopes {
        // §M12 under write-lock against evolving workingState
        if err := rt.checkAdminScopeAgainstWorkingState(env, workingState); err != nil {
            return nil, err
        }
        next, err := fold.Evaluate(env, workingState, time.Now())
        if err != nil { ... }
        workingState = next
    }
    // persist atomically ...
}

This is a bigger diff than I'd like, but it's the clean shape. Alternative (cheaper): reject any batch that contains both an ADD of URN U and a MUTATE of URN U — but that's a real product restriction on atomic programs (e.g. atomic session-birth with properties), and the §M11 fix made the same trade-off in favor of expressive batches.

New test for the fix: TestApplyProgram_M12_IntraBatchADDThenMUTATE_EnforcesAgainstWorkingState — non-admin actor ADDs a node with a kernel-authority property, then MUTATEs it; second envelope must fail §M12 even though the target didn't exist at batch start.

Blocker 2 — AuthorityScope trusts node over type spec

Gemini's session_context.go:333 finding is also correct and also complete-bypass-grade when combined with blocker 1.

if prop, ok := node.Properties[field]; ok {
    return prop.AuthorityScope, true  // ← trusts node; blank = "" = not admin-scope
}

ValidateADD doesn't enforce that AuthorityScope is populated on declared properties. So a malicious (or just buggy) ADD can ship a node with AuthorityScope: "" on a property whose type-spec says authority_scope: "kernel". §M12 reads the live value, sees blank, and the gate doesn't fire.

Fix: fall back to the registry's type spec when the live node's AuthorityScope is empty. Fail-closed posture. Gemini's suggested code is right:

if prop, ok := node.Properties[field]; ok && prop.AuthorityScope != "" {
    return prop.AuthorityScope, true
}
// Fallback to type spec — this is the canonical authority scope
if spec, ok := r.NodeTypes[node.TypeID]; ok {
    if propSpec, ok := spec.Properties[field]; ok {
        return propSpec.AuthorityScope, true
    }
}
return "", false

New test: TestAdminScopeRewrite_MUTATE_NodePropertyMissingAuthorityScope_FallsBackToTypeSpec — pin the fallback. Also a companion: TestAdminScopeRewrite_MUTATE_NodePropertyAndTypeSpecBothBlank_ReturnsFalse (both empty = not admin-scope, consistent with case 2 semantics).

Nit — redundant kernel-type check

Gemini's session_context.go:315 MEDIUM — the node.TypeID != "kernel" check is unreachable because kernel-type nodes exit early at line 304 via the ontology-governed case. Clean simplification; fold in.

Stacking impact — longer-term concern

Both bypasses are live on master from this PR's merge point until fixed. I'd rather not ship PR 31 with them and then chase a PR 31.1 — the same compensating-rewrite doctrine applies: better to land clean than retroactively patch.

If you'd rather ship PR 31 as-is and immediately stack a PR 31.1 with the two fixes + 3 tests, I can re-review 31.1 at the same tempo. Mild preference for folding into 31 directly given both fixes are small, but your call on cadence.

Everything else is clean

The classifier structure, case 1/2/3 split, method-form-on-Registry move, system-internal-allowlist precedence, SeedIfAbsent bypass, prospective-only replay invariant — all exactly as specced in the M11/M12 plan. Test coverage (17 tests) hits the 8 I listed + 9 classifier cases. The Registry-receiver move to support additive MUTATE type-spec lookup is smart.

Fix the two HIGH findings + the nit + add 3 tests (one per bypass + fallback coverage) → fresh LGTM. Same re-review cycle.

— Guido, session:sam.governance

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Implements the §M12 “admin-scope” classifier and wires it into the kernel’s liveness gate so that admin-scope rewrites require WF02 superadmin capability (after the §M11 allowlist/session-resolution steps).

Changes:

  • Add (*operad.Registry).AdminScopeRewrite(env, state) implementing the §M12 classifier (ontology-governed types + kernel-authority field MUTATE on non-kernel nodes).
  • Update kernel liveness gate to call the registry method and enforce operad.CheckAdminCapability when admin-scope is detected.
  • Add operad-level unit tests and kernel-level integration tests covering the §M12 acceptance/rejection matrix and replay invariants.

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 2 comments.

File Description
internal/operad/session_context.go Implements §M12 admin-scope classifier as a *Registry method + helper for authority-scope lookup.
internal/operad/session_context_test.go Adds classifier-focused unit tests for ontology-governed ADD/MUTATE and kernel-authority MUTATE cases.
internal/kernel/liveness.go Switches §M12 gating to use rt.registry.AdminScopeRewrite(...) and enforces superadmin capability when needed.
internal/kernel/liveness_test.go Adds §M12 integration tests validating rejection/acceptance, allowlist precedence, SeedIfAbsent bypass, and replay safety.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread internal/operad/session_context_test.go Outdated
}
}

func TestAdminScopeRewrite_MUTATEKernelAuthorityFieldOnKernelNode_NotAdminScope(t *testing.T) {
Comment thread internal/operad/session_context.go Outdated
Comment on lines +231 to +236
// ontologyGovernedTypes is the set of node types whose ADD or MUTATE
// requires superadmin capability (§M12 Q3 case 2 per ffs0#33 Guido answers).
// Any change to these types influences how the graph itself is grammared —
// they are S2 infrastructure artifacts whose authority must route through
// WF02 governs → role:superadmin, not ordinary occupant authority.
//
Addresses all 5 line-level findings from Gemini + Copilot on #31.

(1) Gemini SECURITY-HIGH — ADD+MUTATE intra-batch bypass (liveness.go:81):

Pre-fix: ApplyProgram ran checkLiveness (including §M12) against the
batch-initial state. A program that ADDed an ordinary node and then
MUTATEd a kernel-authority property on it bypassed §M12 because the
classifier saw the target missing from initial state and returned
false.

Fix: split liveness into two phases with different state-resolution
rules:

  - §M11 (emitter-context) runs in preflight against batch-initial
    state. Emitter references cannot depend on prior envelopes;
    initial-state-check is the documented doctrine (impl-plan §2.4).

  - §M12 (target-operation) runs per-envelope INSIDE the write-locked
    working-state loop in ApplyProgram. By the time envelope N runs,
    all nodes ADDed in envelopes 1..N-1 are visible; target
    classification is correct.

New method split in kernel/liveness.go:

  checkLivenessM11(env, state)  — emitter-context only
  checkLivenessM12(env, state)  — admin-scope only
  checkLiveness(env)            — both against rt.state (Apply helper)

ApplyProgram preflight loop calls checkLivenessM11 only; the working-
state loop adds a checkLivenessM12 call at the top of each iteration.

(2) Gemini SECURITY-HIGH — empty AuthorityScope bypass (session_context.go:333):

Pre-fix: authorityScopeForField trusted the live property's
AuthorityScope unconditionally, even when empty. An ADD that failed to
populate the metadata left the field classified as no-authority-scope
forever.

Fix: fall through to the registry type spec when the stored scope is
empty. The registry is authoritative — trust it over potentially-
missing node metadata. Same pattern the ValidateMUTATE additive path
already uses.

(3) Gemini MEDIUM — redundant kernel exclusion (session_context.go:315):

Pre-fix: case 2 had `if scope == "kernel" && node.TypeID != "kernel"`.
The kernel-type exclusion was redundant because case 1 would have
already returned true for kernel-typed nodes.

Fix: drop the kernel check. Code reads cleaner; behavior unchanged.

(4) Copilot — misleading test name (session_context_test.go:394):

TestAdminScopeRewrite_MUTATEKernelAuthorityFieldOnKernelNode_NotAdminScope
asserted admin-scope=true (via case 1). Renamed to
...MUTATEOnKernelNode_AdminScopeViaOntologyGovernedType.

(5) Copilot — misleading precedence comment (session_context.go:236):

The ontologyGovernedTypes docstring said ADD/MUTATE "requires
superadmin capability" without noting that kernel-actor and
infrastructure-bootstrap envelopes bypass §M12 via the §M11 allowlist
upstream. Added an IMPORTANT block clarifying the precedence.

Tests (4 new):

  operad.TestAdminScopeRewrite_MUTATETargetMissing_FailsClosed
  operad.TestAdminScopeRewrite_EmptyStoredAuthorityScope_FallsBackToTypeSpec
  kernel.TestApplyProgram_M12_IntraBatchADDThenKernelAuthorityMUTATE_Rejected
  kernel.TestApplyProgram_M12_IntraBatchADDThenOwnerMUTATE_Passes

Plus the existing test rename.

21 total §M12-related tests now (9 operad unit + 12 kernel integration).

go build ./...  # clean
go test ./...   # all packages pass

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@MSD21091969
Copy link
Copy Markdown
Collaborator Author

Review fixup pushed as b731822. All 5 Gemini/Copilot findings addressed + two new tests pinning the security fixes.

(1) Gemini SECURITY-HIGH liveness.go:81 — intra-batch ADD+MUTATE bypass

Pre-fix flow:

ApplyProgram preflight (vs initial state):
  env 1 — ADD program:x         → not admin-scope, passes
  env 2 — MUTATE program:x.target_t (kernel-auth)
    AdminScopeRewrite sees program:x MISSING from initial state
      → returns false
    §M12 skipped
  batch accepted, kernel-authority MUTATE landed w/o superadmin

Root cause: checkLiveness evaluated against batch-initial state. §M11 actually WANTS that (emitter-context must pre-exist, documented at impl-plan §2.4). But §M12 evaluates the TARGET operation, which can evolve mid-batch.

Fix: split liveness into two explicit phases with different state-resolution rules:

Phase When State
§M11 emitter-context ApplyProgram preflight (RLock) batch-initial
§M12 admin-scope per-envelope inside the write-locked working-state loop working state after envelopes 1..N-1

New method split in kernel/liveness.go:

(rt) checkLivenessM11(env, state) error   // emitter-context only
(rt) checkLivenessM12(env, state) error   // admin-scope only
(rt) checkLiveness(env) error             // both against rt.state — Apply helper

Apply is unchanged (single envelope, no working state evolves). ApplyProgram preflight loop calls checkLivenessM11 only; the existing working-state loop gets a checkLivenessM12(env, workingState) at the top of each iteration.

(2) Gemini SECURITY-HIGH session_context.go:333 — empty-scope bypass

Pre-fix: authorityScopeForField trusted the stored property's AuthorityScope unconditionally. An ADD that failed to populate the metadata left the field misclassified indefinitely.

Fix: fall through to the registry type spec when stored scope is empty:

if prop, ok := node.Properties[field]; ok && prop.AuthorityScope != "" {
    return prop.AuthorityScope, true
}
// → fall through to registry type spec lookup

Registry is authoritative. Same discipline as the additive-MUTATE path in ValidateMUTATE.

(3) Gemini MEDIUM session_context.go:315 — redundant kernel check

Dropped node.TypeID != "kernel" because case 1 (ontology-governed types) already returned true for kernel-typed nodes upstream. Behavior unchanged, code reads cleaner.

(4) Copilot session_context_test.go:394 — misleading test name

...MUTATEKernelAuthorityFieldOnKernelNode_NotAdminScope asserted admin-scope=true. Renamed to ...MUTATEOnKernelNode_AdminScopeViaOntologyGovernedType. The comment also explains that kernel-typed nodes are admin-scope via case 1, not case 2.

(5) Copilot session_context.go:236 — precedence comment

Added an IMPORTANT block to ontologyGovernedTypes docstring clarifying that the §M11 allowlist (SystemInternalEnvelope) runs before §M12, so kernel-URN actors and infrastructure-ADD bootstrap envelopes bypass §M12 by design. An ADD of type=kernel by a kernel-actor does NOT require superadmin.

Tests (4 new)

  • operad.TestAdminScopeRewrite_MUTATETargetMissing_FailsClosed — pins the fail-closed classification when target is absent from the passed state
  • operad.TestAdminScopeRewrite_EmptyStoredAuthorityScope_FallsBackToTypeSpec — pins the registry fallback
  • kernel.TestApplyProgram_M12_IntraBatchADDThenKernelAuthorityMUTATE_Rejected — end-to-end: the exact Gemini-flagged scenario now rejects with §M12
  • kernel.TestApplyProgram_M12_IntraBatchADDThenOwnerMUTATE_Passes — positive pair: the fix doesn't over-reject; ADD+owner-authority-MUTATE batches still pass

Plus the rename of the previously-misleading test.

21 total §M12-related tests (9 operad classifier + 12 kernel integration).

go build ./...   # clean
go test ./...    # all packages pass

Ready for re-review

Race-condition safety unchanged from PR 30 — §M12 check in the working-state loop runs under the already-held write lock, so no new synchronization is introduced.

Copy link
Copy Markdown
Collaborator Author

@MSD21091969 MSD21091969 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM on b731822. Both SECURITY-HIGH blockers cleanly closed with exactly the shape I recommended.

Blocker closure verification

# Blocker Fix
1 Gemini SECURITY-HIGH liveness.go:81 — intra-batch ADD+MUTATE §M12 bypass Split checkLiveness into M11/M12 phases. §M11 preflight (initial state, emitter rule). §M12 per-envelope in working-state loop (mid-batch ADD visibility). Closes ADD+MUTATE bypass
2 Gemini SECURITY-HIGH session_context.go:333 authorityScopeForField falls through to registry type-spec when stored AuthorityScope is empty. Registry authoritative
3 Gemini MEDIUM session_context.go:315 Dropped redundant node.TypeID != "kernel" check (case 1 already covers it). Bonus fold-in

The new test TestApplyProgram_M12_IntraBatchADDThenKernelAuthorityMUTATE_Rejected is pinned exactly on the scenario that was bypassing: claude (non-admin) ADDs a program + immediately MUTATEs its target_t (kernel-authority field). Pre-fix: accepted. Post-fix: rejected with §M12. Log unchanged on atomic rejection. Behavior change pinned.

The secondary fix on the "target missing from state" path — flipping fail-open → fail-closed (returning true) — is a nice belt-and-braces: even if a future caller passes initial state to AdminScopeRewrite in a context where they should have threaded working-state, the classifier defaults to "this is admin-scope" rather than silently-pass. Fail-closed-by-default is the right posture for a security gate.

Remaining review signal

The two Copilot comments still showing on the thread (test-name misleading at session_context_test.go:394 + comment/code mismatch on "superadmin capability" at session_context.go:236) are both stale against the initial commit a08f0fa, posted at 15:47 before this fixup at 19:20. Your ontologyGovernedTypes docstring rewrite in this fixup actually CLARIFIES the precedence (§M11 allowlist before §M12) in exactly the way the comment-mismatch point wanted. The test-name nit is a rename — author discretion, non-blocking. Either request re-reviews from Copilot/Gemini to clear the signals, or leave them as known-stale and merge.

Merge-ready

Ready to close via merge when you are. Round-11 close from the ffs0 side unblocked:

  1. Rebuild + targeted restart both kernels (§M12 activates alongside §M11)
  2. Running-state T=171/172 close commit on ffs0
  3. Archive round11-pr1-postmerge-compensating-batch.{json,md} to dev/reference/research-archive/
  4. Skill doctrine update (MSD21091969 → Collider-Data-Systems for the org move; I'm handling this in parallel)
  5. v3.13 grammar_fragment proposals from the exploration thread (deferred to future round per sam's "cleanup + new baseline" direction)

Closing this PR from Guido's seat. Next doctrine round opens in a fresh context window since sam's back and flagging a baseline reset.

— Guido, session:sam.governance

@MSD21091969 MSD21091969 merged commit a417e2f into master Apr 21, 2026
@github-project-automation github-project-automation Bot moved this from Todo to Done in mo:os Apr 21, 2026
MSD21091969 added a commit that referenced this pull request Apr 23, 2026
Captures post-round-11 runtime state:

- Ontology v3.12.0 (was v3.11.0) with v3.13 candidates queue.
- Canonical spec path updated: kernel/20260417-t187-kernel-proper.md
  (M1-M20, still live). Archived-reference pointers redirected to
  dev/reference/research-archive/ (foundation-t158, session-channel-
  purpose, S1/S0 substrate lingo, round conversation summaries).
- New "Runtime gates" section: §M11 liveness via checkLivenessM11
  (initial-state preflight, emitter pre-existence rule) + §M12 admin
  via checkLivenessM12 (working-state per-envelope inside ApplyProgram
  loop, catches intra-batch ADD-then-MUTATE bypass from PR #31).
- SystemInternalEnvelope allowlist precedence + SeedIfAbsent bypass
  documented.
- Package-structure entries refreshed: session_context.go, liveness.go,
  AdditionalPortPairs, AdminScopeRewrite method, SessionURN field on
  Envelope, healthz ontology_version from PR #26.
- Test file list expanded with the 5 new files from round 11.
- Actor-discipline table added matching the moos-rewrite-envelope skill.
- Org move note (github.com/Collider-Data-Systems/moos-kernel).

Doc-only; no code changes.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@MSD21091969 MSD21091969 deleted the round11/pr4-m12-admin-cap branch April 26, 2026 12:58
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.

2 participants