feat(case): KERNEL-2/3 lane resolution and mode-aware admission; CATALOG-1 draft lanes
nk48 1.3 owner review (owner-review-ops-warden.json): - KERNEL-2: Executor.admit parses catalog_source and resolves descriptor.lane; refuses a missing, non-active, undelegated or ungraded lane, an owner outside owner_repo, and a mint/change effect on a lane not graded risk high. catalog-routing-acceptance still covers the whole catalog digest. - KERNEL-3: dispatch mode is read from the type's follow_through step (warden_executes/attended). The step must declare both booleans and a lane and agree with the descriptor's lane and owner. Attended: human actor, WARDEN_AGENT_ID unset, parent-session revocation scope, caller lane equal to the step lane. Owner: native delegation, owner_repo == descriptor.owner, no Warden-executed lane and no Warden-brokered mint. - CATALOG-1: draft entries keycape-attended-case-change (OW-1.3-C1), platform-secret-delivery, consumer-owner-deployment, apps-consumer-handover. Not promoted; promotion waits for owner confirmation. - Fixture catalogs and type steps in tests; published routing_lanes coverage and the workload-join count follow the four new not-applicable lanes. PARAMETER_PINS untouched (PIN-1 waits for founder acknowledgment). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Assistant: claude-code Assistant-Model: opus Assistant-Process: 903097@bnt-lap001 Assistant-Session: aa320c8f-c3fa-46e7-8541-0b58e1e45d23
This commit is contained in:
parent
117fec9241
commit
a14376cf62
8 changed files with 363 additions and 13 deletions
|
|
@ -98,7 +98,7 @@ on_apply:
|
|||
# 2026-10-08: platform-supplier-credentials added as a not-applicable path pattern,
|
||||
# so routing not_applicable rose 16 -> 17.
|
||||
classification_coverage:
|
||||
measured: "2026-10-10"
|
||||
measured: "2026-10-17"
|
||||
attribution: self-measured
|
||||
signing_targets:
|
||||
resolved: 0
|
||||
|
|
@ -107,7 +107,7 @@ classification_coverage:
|
|||
routing_lanes:
|
||||
resolved: 4
|
||||
unknown: 22
|
||||
not_applicable: 18
|
||||
not_applicable: 22
|
||||
|
||||
verdict_caching: none
|
||||
input_claim_caching: "registry/flex-auth/production_registry_snapshot.json, rebuilt by scripts/build_flex_auth_registry.py"
|
||||
|
|
|
|||
|
|
@ -10,22 +10,24 @@
|
|||
# declares it, and is null where the field set has not been established --
|
||||
# null means unknown, never 'one field'.
|
||||
|
||||
generated_at: "2026-10-14T20:20:19Z"
|
||||
generated_at: "2026-10-17T10:04:57Z"
|
||||
source: ops-warden/registry/routing/catalog.yaml
|
||||
catalog_revision: "402b008986c3a710c4f5b41bb1007a55c8d4363e"
|
||||
catalog_revision_date: "2026-10-14T22:20:15+02:00"
|
||||
catalog_dirty: false
|
||||
high_risk_lane_count: 35
|
||||
catalog_dirty: true
|
||||
high_risk_lane_count: 38
|
||||
concrete_path_count: 17
|
||||
|
||||
# Graded high but not a single KV address -- a routing pattern, a broker
|
||||
# grant, or a non-KV lane. Nothing here for a policy to deny.
|
||||
no_concrete_path:
|
||||
- consumer-owner-deployment
|
||||
- database-dynamic-credentials
|
||||
- flex-auth-decision-envelope-signing-key
|
||||
- fluid-telegram-operator
|
||||
- informed-decision-sitting-requester-login
|
||||
- inter-hub-bootstrap-ssh
|
||||
- keycape-attended-case-change
|
||||
- keycape-client-secret-generate
|
||||
- net-kingdom-lldap-bind-credential
|
||||
- net-kingdom-privacyidea-admin-token
|
||||
|
|
@ -35,6 +37,7 @@ no_concrete_path:
|
|||
- openbao-shamir-recovery-ceremony
|
||||
- openbao-structure-survey
|
||||
- ops-warden-warden-sign-token
|
||||
- platform-secret-delivery
|
||||
- platform-supplier-credentials
|
||||
- secrets-engine-approval-client-login
|
||||
- secrets-engine-requester-login
|
||||
|
|
|
|||
|
|
@ -1604,3 +1604,89 @@ entries:
|
|||
automatable: false
|
||||
steps:
|
||||
- "This lane covers first generation only and refuses an existing path (exit 3). Rotation is a separate railiance-platform task that does not exist yet; route rotation needs to railiance-platform."
|
||||
|
||||
# CATALOG-1 (nk48 1.3 owner review, 2026-10-17): four lanes for the 1.3
|
||||
# SecurityCaseType oidc-browser-relying-party. Pointer only, draft. Each
|
||||
# stays draft until its owner confirms the pointer
|
||||
# (wiki/playbooks/catalog-lane-promotion.md); the kernel refuses draft lanes.
|
||||
- id: keycape-attended-case-change
|
||||
title: SecurityCase attended KeyCape change (ensure-groups, bind-env, register-client)
|
||||
# OW-1.3-C1: dedicated attended change authority replacing key-cape-oidc-login
|
||||
# for kernel-dispatched KeyCape registry, group and Deployment env changes.
|
||||
# The session may be established by a key-cape OIDC login; that login lane
|
||||
# stays the identity step, not the change authority.
|
||||
risk: high
|
||||
workload_ref:
|
||||
applicability: not-applicable
|
||||
reason: "Per-case attended change lane; the consuming service is a case parameter."
|
||||
need_keywords: [keycape-attended-case-change, ensure-groups, bind-env, register-client, attended keycape change]
|
||||
owner_repo: key-cape/net-kingdom
|
||||
subsystem: key-cape registry and Deployment env, net-kingdom groups
|
||||
warden_executes: false
|
||||
wiki_ref: wiki/playbooks/catalog-lane-promotion.md
|
||||
canon_ref: net-kingdom/security-case-types/oidc-browser-relying-party/type.yaml
|
||||
reviewed: "2026-10-17"
|
||||
status: draft
|
||||
delegation:
|
||||
mode: interim
|
||||
intended_owner: key-cape
|
||||
blocked_on: "key-cape and net-kingdom confirm the pointer and the re-frozen 1.3 descriptors (OW-1.3-C1)."
|
||||
reviewed: "2026-10-17"
|
||||
verified: source-read
|
||||
- id: platform-secret-delivery
|
||||
title: SecurityCase deliver-secret, KeyCape client secret delivered by Platform
|
||||
risk: high
|
||||
workload_ref:
|
||||
applicability: not-applicable
|
||||
reason: "Per-case delivery lane; each case's consuming service gets its own client entry."
|
||||
need_keywords: [platform-secret-delivery, deliver-secret, keycape client secret delivery]
|
||||
owner_repo: railiance-platform
|
||||
subsystem: railiance-platform External Secrets delivery
|
||||
warden_executes: false
|
||||
wiki_ref: wiki/playbooks/catalog-lane-promotion.md
|
||||
canon_ref: railiance-platform/AGENTS.md
|
||||
reviewed: "2026-10-17"
|
||||
status: draft
|
||||
delegation:
|
||||
mode: native
|
||||
intended_owner: railiance-platform
|
||||
reviewed: "2026-10-17"
|
||||
- id: consumer-owner-deployment
|
||||
title: SecurityCase deliver-consumer-secret, consumer deployment by its owner
|
||||
# Descriptor actor is agent: ADR-0004 read boundary applies. Owner-side
|
||||
# delivery only; no raw value reaches the agent session. If that cannot be
|
||||
# shown, the actor changes to human and the railiance-apps digest changes.
|
||||
risk: high
|
||||
workload_ref:
|
||||
applicability: not-applicable
|
||||
reason: "Per-case consumer deployment; the consumer is a case parameter."
|
||||
need_keywords: [consumer-owner-deployment, deliver-consumer-secret]
|
||||
owner_repo: railiance-apps
|
||||
subsystem: railiance-apps consumer deployment
|
||||
warden_executes: false
|
||||
wiki_ref: wiki/playbooks/catalog-lane-promotion.md
|
||||
canon_ref: railiance-apps/AGENTS.md
|
||||
reviewed: "2026-10-17"
|
||||
status: draft
|
||||
delegation:
|
||||
mode: native
|
||||
intended_owner: railiance-apps
|
||||
reviewed: "2026-10-17"
|
||||
- id: apps-consumer-handover
|
||||
title: SecurityCase hand-over to the consuming service owner
|
||||
risk: standard
|
||||
workload_ref:
|
||||
applicability: not-applicable
|
||||
reason: "Per-case hand-over; the consumer owner is a case parameter."
|
||||
need_keywords: [apps-consumer-handover, hand-over, consumer handover]
|
||||
owner_repo: railiance-apps
|
||||
subsystem: railiance-apps consumer hand-over
|
||||
warden_executes: false
|
||||
wiki_ref: wiki/playbooks/catalog-lane-promotion.md
|
||||
canon_ref: railiance-apps/AGENTS.md
|
||||
reviewed: "2026-10-17"
|
||||
status: draft
|
||||
delegation:
|
||||
mode: native
|
||||
intended_owner: railiance-apps
|
||||
reviewed: "2026-10-17"
|
||||
|
|
|
|||
|
|
@ -553,6 +553,67 @@ class Journal:
|
|||
raise
|
||||
|
||||
|
||||
def _catalog_lane(catalog_source: bytes, descriptor: Descriptor):
|
||||
"""KERNEL-2: the descriptor's lane must be an active, fitting catalog entry.
|
||||
|
||||
catalog-routing-acceptance still covers the whole catalog digest; this
|
||||
ties the one lane the descriptor names to an entry inside that digest.
|
||||
"""
|
||||
from .routing.catalog import CatalogError, _parse_entry
|
||||
|
||||
try:
|
||||
raw = yaml.safe_load(catalog_source)
|
||||
items = raw["entries"]
|
||||
if not isinstance(items, list):
|
||||
raise TypeError
|
||||
entries = {}
|
||||
for index, item in enumerate(items):
|
||||
parsed = _parse_entry(item, index)
|
||||
if parsed.id in entries:
|
||||
raise ValueError
|
||||
entries[parsed.id] = parsed
|
||||
except (yaml.YAMLError, CatalogError, KeyError, TypeError, ValueError):
|
||||
raise Refused("invalid catalog source shape") from None
|
||||
entry = entries.get(descriptor.lane)
|
||||
if entry is None:
|
||||
raise Refused("descriptor lane is not in the catalog")
|
||||
if not entry.is_active:
|
||||
raise Refused("descriptor lane is not active in the catalog")
|
||||
if entry.delegation is None:
|
||||
raise Refused("descriptor lane has no catalog delegation record")
|
||||
if not entry.is_graded:
|
||||
raise Refused("descriptor lane carries no risk grade")
|
||||
if descriptor.owner not in entry.owner_repo.split("/"):
|
||||
raise Refused("descriptor owner does not fit catalog lane owner_repo")
|
||||
if descriptor.effect in ("mint", "change") and entry.risk != "high":
|
||||
raise Refused("catalog lane risk does not cover the descriptor effect")
|
||||
return entry
|
||||
|
||||
|
||||
def _step_mode(case_type: dict, case: dict, descriptor: Descriptor) -> str:
|
||||
"""KERNEL-3: warden, attended or owner, from the type step's own text."""
|
||||
steps = [
|
||||
step
|
||||
for step in case_type.get("follow_through") or []
|
||||
if isinstance(step, dict) and step.get("id") == descriptor.step
|
||||
]
|
||||
step = steps[0] if len(steps) == 1 else {}
|
||||
executes, attended = step.get("warden_executes"), step.get("attended")
|
||||
if (
|
||||
type(executes) is not bool
|
||||
or type(attended) is not bool
|
||||
or (executes and attended)
|
||||
or not isinstance(step.get("lane"), str)
|
||||
):
|
||||
raise Refused("type step does not declare how the kernel dispatches it")
|
||||
owner = step.get("owner")
|
||||
if isinstance(owner, str) and re.fullmatch(r"\{[a-z_]+\}", owner):
|
||||
owner = (case.get("parameters") or {}).get(owner[1:-1])
|
||||
if step["lane"] != descriptor.lane or owner != descriptor.owner:
|
||||
raise Refused("type step lane or owner disagrees with descriptor")
|
||||
return "warden" if executes else "attended" if attended else "owner"
|
||||
|
||||
|
||||
class Executor:
|
||||
"""Injected fixed owner adapters and provenance verifier; none ship by default.
|
||||
|
||||
|
|
@ -650,6 +711,21 @@ class Executor:
|
|||
actor != "human" or os.getenv("WARDEN_AGENT_ID")
|
||||
):
|
||||
raise Refused("agent mint is forbidden")
|
||||
entry = _catalog_lane(catalog_source, descriptor)
|
||||
mode = _step_mode(case_type, case, descriptor)
|
||||
if mode == "attended" and (
|
||||
actor != "human"
|
||||
or os.getenv("WARDEN_AGENT_ID")
|
||||
or "parent-session" not in descriptor.revocation_scopes
|
||||
):
|
||||
raise Refused("attended step requires a human parent session")
|
||||
if mode == "owner" and (
|
||||
entry.effective_delegation.mode != "native"
|
||||
or entry.owner_repo != descriptor.owner
|
||||
or entry.warden_executes
|
||||
or descriptor.effect == "mint"
|
||||
):
|
||||
raise Refused("owner step lane is not the owner's native path")
|
||||
parameters = case.get("parameters", {})
|
||||
declared = {
|
||||
item["name"]: item
|
||||
|
|
|
|||
135
tests/test_case_admission_modes.py
Normal file
135
tests/test_case_admission_modes.py
Normal file
|
|
@ -0,0 +1,135 @@
|
|||
"""KERNEL-2 lane resolution and KERNEL-3 mode-aware admission (fixture only)."""
|
||||
from dataclasses import asdict, replace
|
||||
import json
|
||||
|
||||
import pytest
|
||||
import yaml
|
||||
|
||||
from warden.case_receipt_state import Refused
|
||||
from warden.case_run import Descriptor, digest
|
||||
from tests.test_case_run import MINT_STEP, fixture_catalog, lane_entry, make_rig
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def rig(tmp_path):
|
||||
return make_rig(tmp_path)
|
||||
|
||||
EVIDENCE = {"acceptance", "materialization", "descriptor", "session"}
|
||||
|
||||
|
||||
def admit(rig, *, step=None, entries=None, catalog=None, desc=None, **over):
|
||||
executor, _, _, sources, kwargs = rig
|
||||
case, typ, descriptor, _ = sources
|
||||
if desc is not None:
|
||||
base = Descriptor.decode(descriptor)
|
||||
descriptor = json.dumps(asdict(replace(base, **desc))).encode()
|
||||
doc = yaml.safe_load(typ)
|
||||
doc["spec"]["execution_descriptors"] = {"mint-secret": digest(descriptor)}
|
||||
if step is not None:
|
||||
doc["spec"]["follow_through"] = step
|
||||
typ = yaml.safe_dump(doc).encode()
|
||||
if catalog is None:
|
||||
catalog = fixture_catalog(*(entries or [lane_entry()]))
|
||||
executor.evidence_verifier = lambda purpose, source, evidence: evidence in EVIDENCE
|
||||
return executor.admit(case, typ, descriptor, catalog, **{**kwargs, **over})
|
||||
|
||||
|
||||
def mode_step(executes, attended):
|
||||
return [dict(MINT_STEP, warden_executes=executes, attended=attended)]
|
||||
|
||||
|
||||
def refused(message, rig, **kw):
|
||||
with pytest.raises(Refused, match=message):
|
||||
admit(rig, **kw)
|
||||
|
||||
|
||||
# Positive modes
|
||||
def test_warden_mode_admits(rig):
|
||||
assert admit(rig)[1].lane == "issuer-lane"
|
||||
|
||||
|
||||
def test_attended_mode_admits_human_parent_session(rig, monkeypatch):
|
||||
monkeypatch.delenv("WARDEN_AGENT_ID", raising=False)
|
||||
admitted = admit(
|
||||
rig,
|
||||
step=mode_step(False, True),
|
||||
desc=dict(effect="change", revocation_scopes=("parent-session",)),
|
||||
)
|
||||
assert admitted[1].actor == "human"
|
||||
|
||||
|
||||
def test_owner_mode_admits_native_owner_lane(rig):
|
||||
admitted = admit(rig, step=mode_step(False, False), desc=dict(effect="change"))
|
||||
assert admitted[1].owner == "owner"
|
||||
|
||||
|
||||
# KERNEL-2
|
||||
@pytest.mark.parametrize(
|
||||
"entries, catalog, message",
|
||||
[
|
||||
(None, b"not: [a catalog", "invalid catalog source shape"),
|
||||
([lane_entry("other-lane")], None, "descriptor lane is not in the catalog"),
|
||||
([lane_entry(status="draft")], None, "descriptor lane is not active in the catalog"),
|
||||
([{k: v for k, v in lane_entry().items() if k != "delegation"}], None,
|
||||
"descriptor lane has no catalog delegation record"),
|
||||
([{k: v for k, v in lane_entry().items() if k != "risk"}], None,
|
||||
"descriptor lane carries no risk grade"),
|
||||
([lane_entry(owner="someone-else")], None,
|
||||
"descriptor owner does not fit catalog lane owner_repo"),
|
||||
([lane_entry(risk="standard")], None,
|
||||
"catalog lane risk does not cover the descriptor effect"),
|
||||
],
|
||||
)
|
||||
def test_catalog_lane_refusals(rig, entries, catalog, message):
|
||||
refused(message, rig, entries=entries, catalog=catalog)
|
||||
|
||||
|
||||
# KERNEL-3: the step must declare dispatch and agree with the descriptor
|
||||
@pytest.mark.parametrize(
|
||||
"step, message",
|
||||
[
|
||||
([], "type step does not declare how the kernel dispatches it"),
|
||||
(mode_step(True, True), "type step does not declare how the kernel dispatches it"),
|
||||
([{k: v for k, v in MINT_STEP.items() if k != "lane"}],
|
||||
"type step does not declare how the kernel dispatches it"),
|
||||
([dict(MINT_STEP, warden_executes="yes")],
|
||||
"type step does not declare how the kernel dispatches it"),
|
||||
([dict(MINT_STEP, lane="other-lane")], "type step lane or owner disagrees with descriptor"),
|
||||
([dict(MINT_STEP, owner="other")], "type step lane or owner disagrees with descriptor"),
|
||||
],
|
||||
)
|
||||
def test_step_declaration_refusals(rig, step, message):
|
||||
refused(message, rig, step=step)
|
||||
|
||||
|
||||
def test_attended_refuses_agent_session(rig, monkeypatch):
|
||||
monkeypatch.setenv("WARDEN_AGENT_ID", "agent-1")
|
||||
refused("attended step requires a human parent session", rig,
|
||||
step=mode_step(False, True), desc=dict(effect="change"))
|
||||
|
||||
|
||||
def test_attended_refuses_agent_actor(rig):
|
||||
refused("attended step requires a human parent session", rig,
|
||||
step=mode_step(False, True), desc=dict(effect="change", actor="agent"),
|
||||
actor="agent")
|
||||
|
||||
|
||||
def test_attended_refuses_caller_lane_other_than_step_lane(rig):
|
||||
refused("principal actor or lane mismatch", rig,
|
||||
step=mode_step(False, True), desc=dict(effect="change"), lane="login-lane")
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"entry, desc",
|
||||
[
|
||||
(lane_entry(delegation={"mode": "interim", "intended_owner": "owner",
|
||||
"blocked_on": "fixture", "reviewed": "2026-10-17"}),
|
||||
dict(effect="change")),
|
||||
(lane_entry(owner="owner/other"), dict(effect="change")),
|
||||
(lane_entry(warden_executes=True), dict(effect="change")),
|
||||
(lane_entry(), {}), # owner step may not be a Warden-brokered mint
|
||||
],
|
||||
)
|
||||
def test_owner_mode_refusals(rig, entry, desc):
|
||||
refused("owner step lane is not the owner's native path", rig,
|
||||
step=mode_step(False, False), entries=[entry], desc=desc)
|
||||
|
|
@ -44,6 +44,9 @@ def browser(rig, version="1.1", mutate=None):
|
|||
case["spec"]["parameters"]["consumer_namespace"] = "forgejo"
|
||||
# This extension and all native provenance are TEST ONLY, never source assent.
|
||||
typ["spec"]["execution_descriptors"] = {"mint-secret": digest(sources[2])}
|
||||
for step in typ["spec"]["follow_through"]:
|
||||
if step["id"] == "mint-secret": # TEST ONLY: route to the fixture lane
|
||||
step.update(owner="owner", lane="issuer-lane")
|
||||
if mutate:
|
||||
mutate(case, typ)
|
||||
changed = (
|
||||
|
|
|
|||
|
|
@ -12,8 +12,47 @@ from warden.case_receipt_state import Outcome, Refused
|
|||
from warden.case_run import Adapter, Descriptor, Executor, Journal, OwnerResult, digest
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def rig(tmp_path):
|
||||
def lane_entry(lane="issuer-lane", owner="owner", **over):
|
||||
"""Fixture catalog entry (TEST ONLY; never a real routing pointer)."""
|
||||
entry = {
|
||||
"id": lane,
|
||||
"title": "fixture lane",
|
||||
"need_keywords": [lane],
|
||||
"owner_repo": owner,
|
||||
"subsystem": "fixture",
|
||||
"warden_executes": False,
|
||||
"wiki_ref": "wiki/fixture.md",
|
||||
"canon_ref": "fixture/canon.md",
|
||||
"reviewed": "2026-10-17",
|
||||
"status": "active",
|
||||
"risk": "high",
|
||||
"workload_ref": {"applicability": "not-applicable", "reason": "fixture"},
|
||||
"delegation": {
|
||||
"mode": "native",
|
||||
"intended_owner": owner,
|
||||
"reviewed": "2026-10-17",
|
||||
},
|
||||
}
|
||||
entry.update(over)
|
||||
return entry
|
||||
|
||||
|
||||
def fixture_catalog(*entries):
|
||||
return yaml.safe_dump({"entries": list(entries) or [lane_entry()]}).encode()
|
||||
|
||||
|
||||
CATALOG = fixture_catalog()
|
||||
|
||||
MINT_STEP = {
|
||||
"id": "mint-secret",
|
||||
"owner": "owner",
|
||||
"lane": "issuer-lane",
|
||||
"warden_executes": True,
|
||||
"attended": False,
|
||||
}
|
||||
|
||||
|
||||
def make_rig(tmp_path):
|
||||
desc = Descriptor(
|
||||
"warden.case.execution.v1",
|
||||
"mint-secret",
|
||||
|
|
@ -43,6 +82,7 @@ def rig(tmp_path):
|
|||
}
|
||||
],
|
||||
"execution_descriptors": {"mint-secret": digest(descriptor)},
|
||||
"follow_through": [dict(MINT_STEP)],
|
||||
}
|
||||
}
|
||||
).encode()
|
||||
|
|
@ -106,7 +146,7 @@ def rig(tmp_path):
|
|||
("ops-mason-materialization", digest(case), "materialization"),
|
||||
("owner-descriptor-acceptance:owner", digest(descriptor), "descriptor"),
|
||||
("type-machine-seam-acceptance", digest(typ), "descriptor"),
|
||||
("catalog-routing-acceptance", digest(b"catalog"), "descriptor"),
|
||||
("catalog-routing-acceptance", digest(CATALOG), "descriptor"),
|
||||
("caller-session:human:operator:issuer-lane", digest(case), "session"),
|
||||
("session-revocation:issuer-lane", digest(case), "session-revoked"),
|
||||
("grant-revocation:issuer-lane", digest(case), "grant-revoked"),
|
||||
|
|
@ -136,8 +176,13 @@ def rig(tmp_path):
|
|||
descriptor_evidence="descriptor",
|
||||
session_evidence="session",
|
||||
)
|
||||
admitted = executor.admit(case, typ, descriptor, b"catalog", **kwargs)
|
||||
return (executor, admitted, calls, (case, typ, descriptor, b"catalog"), kwargs)
|
||||
admitted = executor.admit(case, typ, descriptor, CATALOG, **kwargs)
|
||||
return (executor, admitted, calls, (case, typ, descriptor, CATALOG), kwargs)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def rig(tmp_path):
|
||||
return make_rig(tmp_path)
|
||||
|
||||
|
||||
def test_commit_running_is_visible_from_separate_connection_before_dispatch(rig):
|
||||
|
|
@ -387,7 +432,7 @@ def test_changed_catalog_pins_cannot_inherit_old_success(rig):
|
|||
executor, args, calls, sources, kwargs = rig
|
||||
executor.run(*args)
|
||||
with pytest.raises(Refused):
|
||||
executor.admit(*sources[:3], b"changed-catalog", **kwargs)
|
||||
executor.admit(*sources[:3], fixture_catalog(lane_entry(title="changed")), **kwargs)
|
||||
assert len(calls) == 1
|
||||
|
||||
|
||||
|
|
@ -601,7 +646,7 @@ def test_attempt_identity_changes_with_exact_catalog_revision_and_new_journal(
|
|||
executor, args, calls, sources, kwargs = rig
|
||||
executor.run(*args)
|
||||
original_attempt = calls[-1].attempt_id
|
||||
new_catalog = b"independently-reviewed-catalog-v2"
|
||||
new_catalog = fixture_catalog(lane_entry(title="independently reviewed v2"))
|
||||
old_verifier = executor.evidence_verifier
|
||||
|
||||
def verifier(purpose, source, evidence):
|
||||
|
|
|
|||
|
|
@ -28,7 +28,9 @@ def test_repo_catalog_uses_only_explicit_workload_references():
|
|||
# names fluid-telegram, which has published no workload declaration.
|
||||
# not_applicable 18 since 2026-10-10: keycape-client-secret-generate is a
|
||||
# per-case generation lane; each case's consumer gets its own entry.
|
||||
assert len(report["not_applicable"]) == 18
|
||||
# not_applicable 22 since 2026-10-17: the four CATALOG-1 draft lanes
|
||||
# (nk48 1.3 owner review) are per-case lanes like the one above.
|
||||
assert len(report["not_applicable"]) == 22
|
||||
assert {row["lane"] for row in report["resolved"]} == {
|
||||
"ops-warden-warden-sign-token",
|
||||
"issue-core-ingestion-api-key",
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue