Skip to content

test(debate): characterize retired conductor roster rejection - #17

Draft
RMANOV wants to merge 28 commits into
mainfrom
agent/c3-governance-a1
Draft

RMANOV wants to merge 28 commits into
mainfrom
agent/c3-governance-a1

Conversation

@RMANOV

@RMANOV RMANOV commented Sep 13, 2026

Copy link
Copy Markdown
Owner

Summary

Adds two isolated tests through the real public debate wrappers:

  • A new roster containing the retired CONDUCTOR role must be rejected without leaving any rows in the five checked debate tables.
  • An ordinary numbered-executor legacy topic must still support creation, an attributed ACTIVE transition, an addressed STATUS post, and exact-message readback without a governance pin.

The fixture uses a temporary database, real transaction boundaries, and synthetic identities; it disables external wake signaling.

Verification intent

This is an intentional RED characterization, not a production implementation. The expected failure is the retired-roster assertion because current code accepts the INIT roster. The positive availability guard and existing suite must continue to pass. A setup, import, priority, or authorization error is not acceptable RED evidence.

No production files, role bindings, live database, runtime configuration, or deployments are changed. This draft must not be merged while intentionally RED.

RMANOV and others added 28 commits September 14, 2026 01:58
Replace approved live actor and recipient roles with ADVOCATE_CODEX while preserving opaque session IDs, payloads, history, and test expectations. Keep the priority-source fixture and all governance consumers on hold.
Update five approved fixtures, preserve historical SQL and protocol roles, and use an explicitly suppressed OBSERVER in zero-paste tests. Production authority and pump defaults remain unchanged.
…roles, v3.10 lifecycle)

Convert the three mechanics-only fixture files that still created
live-style CONDUCTOR rosters to the branch convention (CONDUCTOR ->
ADVOCATE_CODEX, same session ids), so the retired-role gate no longer
fails them at setup. The init metadata round-trip test now expects the
server-stamped governance candidate record next to the caller's keys
(C3 F06 contract).

Tests that assert CONDUCTOR-specific production behaviour stay RED on
purpose and belong to later phases: conductor_override_msg_id paths
(phase A), set_topic_priority and bin/task fb self-heal (phase B), the
priority source label (phase B5). No production file changes; the F0
governance tests are untouched.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…/post guards, serializer)

Add stdlib-only debate_governance.py: canonical JSON with domain-separated
digests, the bounded strict payload decoder (UTF-8 byte bound measured
before parsing, duplicate keys and non-finite constants rejected, object
root only), migration-manifest normalization and digest check, the frozen
binding version fingerprint, stored-payload recognition and the one legacy
read serializer shared by read_messages and debate_signal_check.

schema.py gains the append-only debate_authorization_spends ledger
(composite key, manifest CHECK, update/delete abort triggers) in the base
DDL, so every init_db path including the debate/v1 rebuild carries it.

debate.py: init_debate refuses caller-supplied governance authority fields
before the idempotency lookup and stamps the server-derived legacy
candidate record on creation; post_message validates governance candidates
on unconfigured topics after author provenance and before the v1 preflight,
with typed zero-row rejections (authority_unconfigured for a well-formed
grant, since no authority can be pinned yet). No positive persistence path
exists in this phase; issuer stamping belongs to the next phase.

Intentionally still RED for later phases: conductor_override paths in
test_debate_flexible_roles and test_debate_v3_10_lifecycle (phase A),
test_debate_priority and test_daily_dashboard::test_bin_task_fb_* (phase
B), the priority source label in test_debate_init (phase B5).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
… gate)

The F0 governance module is a new top-level module; the editable-install
manifest gate requires every top-level .py file to be listed so the
production MCP entrypoints import the same mapping CI tests.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…audited contract

Restore signature parity with the ROOT-audited foundation delta:
validate_legacy_governance_post(conn, *, topic_id, role, kind, reply_to,
payload_json, body_mode, author_session_id, recipients). The extra
authority keyword is removed; the function now resolves the topic's
governance mode itself through the caller's transaction (_pinned_authority),
which is the only DB read in the module and still yields
authority_unconfigured for every topic, since no pin operation exists yet.
The single call site in post_message passes the connection it already
holds. Behaviour and error spellings are unchanged.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Completes the signature-parity change of the previous commit: the module
imports sqlite3 for the connection annotation and the final branch calls
_pinned_authority(conn, topic_id) instead of the removed authority
keyword. ruff F821 x3 cleared; behaviour unchanged (authority_unconfigured
for every topic until a pin operation exists).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…ce record

Adds tests/test_debate_governance_legacy_topics.py (test-only): a
hand-seeded historical topic whose metadata is NULL, empty, lacks the
governance key, carries a non-object governance value, or carries a plain
legacy record must refuse a well-formed authorize DECISION through both
public writers with the typed authority_unconfigured error and zero writes.
A second node pins the current contract that a DECISION with an opaque,
non-governance payload on an unpinned legacy topic is refused typed
(governance_schema_unsupported) rather than posted with the payload
silently dropped; recorded as a decision item for ROOT.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…ller governance key

Round-2 items from the peer review of the F0 foundation:
- is_legacy_governance_candidate no longer parses a non-DECISION payload
  that carries the governance marker but exceeds the 65536-byte bound; it
  is a candidate by size alone and the strict decoder refuses it as too
  large (no unbounded json.loads before the bound).
- reject_caller_governance refuses the whole caller-supplied governance
  key, not only server fields: the record is server-derived and a partial
  object would otherwise be silently overwritten.
- expires_at message reworded to what is checked in this phase (shape
  only; parsed where a grant is consumed).
- Test nodes: oversize marker payload on STATUS -> governance_payload_too_large;
  authority-looking seeded metadata (mode only / complete record) ->
  governance_action_not_implemented; caller governance object at init ->
  governance_server_field_supplied; debate/v1 topic + issuer-stamped-looking
  DECISION -> SEMANTIC_KIND_REQUIRED (characterization for ROOT Q5).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…6/M3)

- reject_caller_governance details now carry value_is_null so a caller
  passing "governance": null can tell the refusal from a field supply.
- New nodes: "governance": null at init -> governance_server_field_supplied
  with fields ["governance"] and value_is_null true, zero rows; a same-roster
  re-init that echoes the STORED metadata (server governance record) is
  refused before the idempotent short-circuit, while a re-init with the
  caller's original metadata still returns the existing row unchanged.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Test-only commit; intentional per-node RED until A2 implements the chain.

- tests/test_debate_governance_f1.py (new, 51 nodes): P0 inventory, P1 HUMAN
  bootstrap via private manifest, F19 loader refusals (symlink, non-regular,
  mode, parent mode, digest, expiry, expiry bound, owner=geteuid, topic drift,
  win32 gate), P2 approve_pin as a manifest tool (single-use digest, reader
  cannot replay, pin epoch 0->1, one spend, fault injection, pin_record_backed),
  P3 authorize + consume (one spend, authorization_required, alias + conflict,
  F13 mismatches, F14 replay, F15 re-pin stale, F17 race + rollback),
  F18 immutability by the server-only governance_schema column (update/delete,
  nested quote deletable, migration ordering, repeat init_db), M-A2, M-A3, D-A2.
- lifecycle/flexible: the four override tests use the real chain
  (bootstrap -> approve_pin -> pin -> authorize -> bind with
  authorization_msg_id); the invariant guard now expects authorization_required.
- foundation F09: stored-format fixture carries governance_schema; new
  negative id shape_without_column (shape alone is never authority).

Every governance import is inside fixtures/nodes: missing surfaces fail
per node, never at collection.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
… fixture fix

Test-only follow-up to 29710ba (EXECUTOR R1 06ff4fce261e, DA W40 3233bd5f1b68):

- fixture: topic metadata carries priority_reason (the A1 run failed every
  node at debate_init with topic_priority_reason_required)
- M1: exactly one error_type per node (governance_payload_invalid,
  governance_action_not_implemented, governance_server_field_supplied)
- M3: test_p2_approve_to_pin_window_is_cas_guarded x4 (authority retired /
  rotated -> authorization_target_changed; HUMAN retired / rotated ->
  authorization_issuer_stale), zero spends, epoch unchanged

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
… the type

Test-only follow-up to 0ba239c (DA W42 §3(a)/(b), W44 placement (i)):
- M-A2 node: the internal_error must come from the unknown-keyword TypeError
  (asserts "unexpected keyword argument" and the key name in `error`)
- generic-bootstrap nodes: the refusal must name the reserved type
  (details.type == "bootstrap_human")
No node added, removed or renamed; pinned line unchanged (61F/4E/1899P).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…ned topic

Test-only follow-up to e2c96b3 (DA W45 §2(a) stop-and-post e5f52491234d):
test_f13_grant_mismatches_apply_nothing[foreign_topic] could never turn green
because the grant was posted on the default topic with a payload naming
C3F1B (and C3F1B was never pinned). Now `_chain` threads the topic through
the bootstrap/approve/pin helpers and `_grant` posts on the topic its payload
names, so the case issues a REAL grant from C3F1B's own pinned authority and
consumes it on C3F1 → authorization_not_found.
No node added, removed or renamed; pinned line unchanged (61F/4E/1899P).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…de, signal helper

Test-only follow-up to c02221c (DA W47/W48, addendum v1.3.1):
- legacy_topics: a raw-SQL authority record is not a pin (D-A1) — the
  well-formed authorize on it is refused governance_pin_unbacked, before
  any actor/issuer check (was governance_action_not_implemented in F0)
- f1: test_f13_authorize_payload_naming_another_topic_is_refused_by_the_validator
  keeps the validator refusal ("authorize topic_id must equal the posting
  topic") under its own id (W47 §2)
- f1: the `_signal` helper reads debate_signal_check's `pending` list
Pinned line: 65F/4E/1896P (1965 nodes).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
… v1.3.1)

Implementation only; the RED matrix (29710ba, 0ba239c, e2c96b3, c02221c,
efe39c1) is untouched.

- debate_governance.py: private-manifest loader (platform gate first,
  O_NOFOLLOW, st_uid == geteuid, mode/parent/size, bounded read, digest,
  expiry ≤ 24 h), projections (binding version/issuer, topic fingerprint,
  spend target key), pin_record_backed / require_pin_record_backed
  (governance_pin_unbacked, v1.3.1), stamp_authorize (server-stamped issuer,
  validity bound), column-based serializer
- schema.py: debate_messages.governance_schema (base + rebuild DDL, ALTER
  migration in the A0 pattern, copy statement); immutability triggers
  (UPDATE without column list + DELETE, WHEN old.governance_schema IS NOT
  NULL) created only in _create_debate_message_indexes_and_triggers, after
  the ALTER; rebuild drop list names all six triggers
- debate.py: governance_inventory / governance_bootstrap_human /
  governance_approve_pin / governance_pin / authorize_and_apply /
  governance_receipt; bind_role_session consumes grants (retire_binding,
  diagnostic_uncover) through authorize_and_apply with exactly one spend,
  authorization_required otherwise, conductor_override_msg_id as a
  deprecated alias; post_message internal_governance (DAO-internal) and the
  governance_schema INSERT column; both readers select the column; row ts
  parsed with fromisoformat (fractional seconds), grant expires_at strict
- intel_server.py: debate_governance_inventory / _bootstrap_human /
  _approve_pin / _pin / _receipt tools; debate_bind_role gains
  author_session_id and authorization_msg_id
- debate_ops.py: governance-inventory / -bootstrap-human / -approve-pin /
  -pin subcommands (thin wrappers)
- CHANGELOG.md: Added / Changed entries (ownership_gap_override alias note)

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…uard, changelog scope

EXECUTOR round 1 7e67fb325d48 + DA W49 2ca92fc41dc3; implementation only:
- M1: bind_role_session consumes a supplied grant ONLY when the call
  uncovers the ACTIVE owner (diagnostic and retire branches); otherwise the
  grant stays unspent and the result carries authorization_msg_id=None,
  spend=None. Contract line: ownership_gap_override True ⇔ a grant was
  consumed.
- M3: the three manifest WRITE tools share one topic loader that refuses
  configured debate/v1 topics (governance_action_not_implemented,
  details.reason=protocol_topic).
- M5: CHANGELOG narrowed to bind_role_session; the diagnostic-recipient
  override in debate_post_with_recipients is unchanged in this phase.
Serializer untouched (v1.3 C1 as committed in 0610355).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…F17)

DA W51 ruling 3c95b5f3d05c on top of A2b (W49 M1): with a grant supplied and
nothing to uncover, the FIRST and ONLY lookup is the consume path's own
_spend_row(authorization_msg_id, SPEND_TARGET_SINGLE) — spent →
authorization_consumed; unspent → grant left untouched, authorization_msg_id
None, spend None, ownership_gap_override False.  Both bind branches
(diagnostic, retire) and the conductor_override_msg_id alias route.  No new
error name.  The lenient consequence is written into the bind_role_session
docstring: on the no-uncover path the grant is ignored, not validated — the
r4 consume chain does not run there.

Implementation only (zero hunks under tests/).  Pinned before the run:
5 failed / 4 errors / 1956 passed (F09 v1 shape + surface guard + dashboard 2
+ init 1; priority 4 E).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…tools), F09 v1 row without the column

Separately pinned test change after A2 (DA W47/W49/W51): the frozen-matrix
regime forbids test hunks inside implementation commits, so the two findings
from the A2 readback land here on their own.

1. tests/fixtures/surface_contract_snapshot.json: regenerated with
   `python tests/test_surface_contract_guard.py --regenerate` — exactly the
   five new names, each twice (per-server + aggregate):
   debate_governance_approve_pin, debate_governance_bootstrap_human,
   debate_governance_inventory, debate_governance_pin,
   debate_governance_receipt.  +10 lines, nothing removed.
2. tests/test_debate_governance_foundation.py::test_f09_v1_read_shape_is_unchanged:
   the debate/v1 fixture row no longer carries governance_schema — a v1 row is
   never classified by the server column (phase A serializer rule, ruled to
   stay).  One line.

Pinned before the run: 3 failed / 4 errors / 1958 passed (dashboard 2 + init 1;
priority 4 E) — the seven residual nodes of phases B1/B4/B5.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Docstring only, no behaviour change: bind_role_session now states exactly
what the no-uncover path checks — replay of a spent single-target grant
(a debate_authorization_spends row for the id with target_key 'single') is
refused with authorization_consumed even when no uncover occurs; no other
property of the grant is checked there.  The earlier 'IGNORED, not
validated' wording overstated the leniency (W54 C-A).  Zero hunks under
tests/.  Pinned: 3 failed / 4 errors / 1958 passed, 30 skipped
(F+E+P+S = 1995, unchanged since A1e).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…-uncover path (DA W57)

Docstring only, no behaviour change (OPTION K ruled: a grant-attached retry
after the first attempt landed stays authorization_consumed; idempotency
belongs to the client read of debate_binding_list, a grant-less retry cannot
uncover).  Disclosure direction stated: spent-vs-unspent of a grant id is
observable to any caller holding the id, existence is not; the error carries
only the error name and the id (every authorization_consumed raise in
debate.py passes no details).  Zero hunks under tests/.  Pinned: 3 failed /
4 errors / 1958 passed, 30 skipped (F+E+P+S = 1995).

Co-Authored-By: Claude Fable 5.1 <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