diff --git a/.github/workflows/full-public-smokes.yml b/.github/workflows/full-public-smokes.yml index 2f990774fd..48a1971a4b 100644 --- a/.github/workflows/full-public-smokes.yml +++ b/.github/workflows/full-public-smokes.yml @@ -73,6 +73,9 @@ jobs: - name: Install smoke runtime dependencies run: python -m pip install --disable-pip-version-check "jsonschema>=4.23,<5" + - name: Install locked TypeScript parser for semantic production checks + run: npm ci --ignore-scripts + - name: Preview full-public shard run: | python3 examples/run-smokes.py \ diff --git a/.gitignore b/.gitignore index 0d598a192d..681d2da831 100644 --- a/.gitignore +++ b/.gitignore @@ -37,3 +37,6 @@ goals/**/ACTIVE_GOAL_STATE.md goals/**/ACTIVE_GOAL_STATE.md.lock /runtime/ logs/ + +# Legacy semantic census reports are derived locally, never repository authority. +/loopx/semantics/inventory_v0.json diff --git a/docs/architecture/rfcs/semantic-vocabulary-convergence-v0.md b/docs/architecture/rfcs/semantic-vocabulary-convergence-v0.md index ec9e0c0470..d660aa6997 100644 --- a/docs/architecture/rfcs/semantic-vocabulary-convergence-v0.md +++ b/docs/architecture/rfcs/semantic-vocabulary-convergence-v0.md @@ -1,13 +1,13 @@ # RFC: Semantic Vocabulary Convergence and Commit-Time Drift Checks (v0) - **RFC status:** Draft -- **Delivery maturity:** Partial (M0 registry, generated inventory, and drift smoke ship with this RFC) +- **Delivery maturity:** Partial (M0 registry, computed inventory, and drift smoke ship with this RFC) - **Authors / owners:** LoopX contributors; control-plane kernel maintainers own approval - **Created:** 2026-09-15 -- **Last normative revision:** 2026-09-15 +- **Last normative revision:** 2026-09-16 - **Implementation baseline:** `1dc6ad8d8` - **Related contracts:** `loopx/semantics/vocabulary_v0.json`, - `loopx/semantics/inventory_v0.json`, + `loopx/semantics/inventory.py`, `loopx/control_plane/turn_transaction_contract.json`, `loopx/control_plane/coordination/coordination_state_contract_v0.json`, [Turn Envelope v0](../../reference/protocols/turn-envelope-v0.md), @@ -34,30 +34,32 @@ not amend normative sections. ## 1. Decision summary -1. **What becomes authoritative.** Two files under `loopx/semantics/`. The +1. **What becomes authoritative.** A curated registry and a computed inventory under `loopx/semantics/`. The curated registry `vocabulary_v0.json` names each kernel and cross-runtime vocabulary, the exact `module::Symbol` allowed to define it, the relations between vocabularies (same concept, shared field name, subset), the total projections, and the budgets the repository ratchets down. The generated - inventory `inventory_v0.json` maps every closed-set carrier under `loopx/`: + inventory maps every closed-set carrier under `loopx/`: string enums, `Literal` aliases, named closed sets, TypeScript `as const` arrays, and every constant name defined in more than one module. A public smoke, `examples/semantic-vocabulary-drift-smoke.py`, checks the code against both inside the default `pytest` sweep on every pull request; premerge and the full-public fleet are additional surfaces (Section 10). A change that widens a - vocabulary, forks a constant, adds a carrier, or weakens the registry must - edit the registry or regenerate the inventory in the same diff, so the - reviewer sees the semantic change as a change. -2. **What remains unchanged.** Runtime behavior, wire formats, and the enum - classes themselves. Each enum keeps living in its owner module; the registry - is checked against code by AST and text scan, it does not generate code and - product code never imports it. + vocabulary, forks a constant, or changes a budget must carry the required + owner/registry edits in the same diff. Ordinary new carriers are discovered + automatically and do not require a generated snapshot commit (Q9). +2. **Authority and generation.** Each enum lives in its owner module; the + registry is checked against code by AST and text scan, and product code never + imports it. The M1 generator derives the TypeScript effective-action binding + from the Python owner after checking registry parity. This does not change + wire values or move value authority to the registry; M2's shared contract + generation remains a later milestone. 3. **Default and opt-in boundary.** The check is always on for the repository. It has no runtime flag because it never runs inside the product. 4. **Principal constraint.** Fail closed, deterministic, and not weakenable by a data edit alone. An unregistered literal in either runtime, a second defining module, a budget overrun, a registry value no module carries, a - stale inventory, an owner declared without a symbol, or a coverage count + stale generated binding, an owner declared without a symbol, or a coverage count below the recorded floor each fails the smoke. The dispatch forms the scan recognises live in the smoke, not in the registry. The smoke reads only tracked sources and prints no private data. @@ -172,8 +174,9 @@ the TypeScript runtime each own one spelling of the same idea. compares with `<=`, which lets a budget tightened below the anchor be raised back to the anchor later without any code edit. Equality makes every tightening a two-file diff and every loosening a code edit a reviewer sees. -- **I6 Same-diff visibility.** A semantic change and its registry edit or - inventory regeneration land in one reviewable diff. +- **I6 Same-diff visibility.** A semantic change and its required owner, registry or budget + edits land in one reviewable diff. Computed inventory reports are evidence, + not committed authority. - **I7 Deterministic and public-safe.** The check reads tracked sources only, needs no network or credentials, and its failure text names files and values, never private data. @@ -181,7 +184,7 @@ the TypeScript runtime each own one spelling of the same idea. symbols, projections, relations, schema versions, and scanned suffixes is recorded as a floor. An owner is `module::Symbol` or `null`; a bare module path is rejected, and a null owner requires a literal scan. The dispatch - forms the scan recognises are fixed in the smoke. A registry edit therefore + forms the scan recognises are fixed in the scanner code. A registry edit therefore cannot silently narrow what the guard sees. - **I9 Both carrier shapes are measured.** A vocabulary reaches the code either as a string constant (`NAME = "value"`) or as a multi-value carrier (an enum, @@ -205,7 +208,7 @@ the TypeScript runtime each own one spelling of the same idea. producer and must be registered as one. Enforced from M0.5. - **I12 Every kernel value is produced.** For a `kernel` vocabulary, every value not listed under `compatibility_only` has at least one production site - the fixed production forms recognise or a `variable_sourced_values` entry. A + the fixed production forms recognise or an executable witness at a registered input decoder. A variable-source note alone is not production evidence. A value that is only compared is dead or compatibility-only, never canonical. `skip` in `effective_action` is the first expected failure. Enforced from M0.5; at M0 the literal scan accepts a compared value as carried. @@ -226,7 +229,7 @@ the TypeScript runtime each own one spelling of the same idea. ### In scope - The registry file, its schema, and the ownership rule for editing it. -- The generated inventory, its generator with `--check`, and its unit test. +- The computed inventory, optional report export/check commands, and their tests. - The drift smoke and its placement in the premerge and full-public fleets. - The vocabularies registered at M0: the four Turn-kernel sets (`turn_result_kind`, `turn_route`, `loop_disposition`, `effective_action`), @@ -315,10 +318,10 @@ definitions in `global_risks.py`, `global_todos.py`, `summary_all.py`, and `pr_review.py` each list the data sources of that one CLI command, and the value sets are meant to differ. It is counted in `multi_value_forks` today and must not be "fixed" by renaming, because a rename lowers the number without -changing the code's meaning. M0.5 adds a `scope` field to the registry with at -least `global` and `bounded_context`, lets a bounded-context name be declared -once with its owning contexts, and removes declared names from the fork -budget (I14, the schema rows below, and the M0.5 row in Section 11). Until +changing the code's meaning. The M0.5 scope slice adds top-level `scope_declarations` with at +least `global` and `bounded_context`; a bounded-context name is declared once +with its owning contexts, and declared names are removed from the semantic +fork budget while the raw inventory count remains visible (I14, the schema rows below, and the M0.5 row in Section 11). Until then the fork budget is a ceiling that contains this one known misclassification, recorded in the registry's `inventory_ratchets` note. @@ -360,6 +363,147 @@ when a value is added or removed after M0.5; `cross_module` only if promoted whose listed symbol is a journal or receipt writer marks the vocabulary `persisted`, which is the fact Q2 and Q10 wait on. +### Executable production evidence during M0.5/M1 + +The producer guard and the owner-carrier check have separate evidence. Defining +an enum member proves membership, not production. For each vocabulary with +producer metadata, the guard compares observed result values against `values`, +rejects undeclared **function sites**, and checks that every non-compatibility +value has an observed producer. A variable-source note is not liveness evidence. +`return_producers` lists the registered functions whose scalar return expressions +belong to this vocabulary; packet builders' unrelated return text is excluded. +`return_paths` selects an explicit field/index path from a returned packet. +`call_producers` names reviewed builder parameters; their module binding and +actual signature are checked against tracked source. These declarations and +their code anchors move together. Selectors equal the code-owned map, with an +empty map for every other vocabulary, so additions also require a code change. +An unconfirmed field-named keyword argument retains closedness/unknown evidence +but cannot establish producer liveness or require producer registration. These +are reviewed output contracts, not automatic proofs of arbitrary helper-body +semantics. Local enum containers and +arguments to arbitrary predicates do not establish production; a selected +scalar must reach an observed output. Mutated or escaped mutable aliases remain +unknown. Generation uses strict enum extraction and rejects unsupported members +before writing any artifact, including when the second owner is invalid. + +Python field assignments (including subscript/attribute and annotated writes), +dictionaries, call keywords, owner-member results and declared scalar returns +are parsed with AST. Imported enum aliases resolve only to the registered owner; +shadowed names, reassignments and unresolved calls remain unknown. Conditional +results exclude the condition's literals. TypeScript object writes, assignments +and declared returns use the repository's TypeScript parser rather than regex. +Neither parser executes inspected source. These are syntactic result witnesses, +not a proof of reachability or whole-program data flow. + +`uv run python examples/semantic-vocabulary-drift-smoke.py --report` lists unresolved +production locations. Unresolved parts cannot supply missing value evidence; known +conditional branches remain structural witnesses, not reachability proofs. +The producer guard covers all six kernel entries using distinct evidence lanes: +`effective_action`, `turn_route`, `loop_disposition`, and +`agent_scope_frontier_action` have source witnesses; `turn_result_kind` also has +executable input witnesses at the fixed `transaction._result_kind` decoder. +For each registered value the real decoder must return the matching typed member; +invalid probes must report rejection. This proves a permitted production path, +not that a Host has emitted every member or that every host execution is valid. +`input_producer` cannot select arbitrary code: the verifier is fixed in the smoke. + +`lease_action` is explicitly legacy/compatibility-only: in-repository runtime +callers use separate acquire/renew/transfer/release command classes. Its four +members remain available to the existing typed `LeaseModeGateCommand` input +interface until M4 caller/migration review. No persisted usage is asserted. +The producer list is empty only because every value carries an explicit reason +and retirement milestone. A newly observed producer invalidates that declaration. Kernel families without producer metadata are printed as coverage pending; their +owner parity must not be reported as I12/I13 completion. M0.5 remains incomplete +until all required families meet its acceptance rows. + +The decision owner includes five existing results previously missed by the +literal scanner: `blocked_health`, `blocked_wait`, `control_plane_repair`, +`operator_gate_notify`, and `throttled_skip`. Registering them preserves the +existing quota behavior. M1 removes the unproduced `skip` and synthetic +`operator_gate` admission. The fallback consumer now recognizes the actual +`quota_skip` action; a runnable scoped fallback must not keep a skip action. +Legacy field retirement remains a separate acceptance obligation. + +Preparation for the TypeScript parser: `npm ci --ignore-scripts` from the +repository root, using its lockfile. The scan itself needs no network or +credentials. Python 3.11+ and the repository-supported Node runtime are required. + +### M1 action domains and compatibility + +#### Why this stage is necessary + +M1 makes callers read the appropriate field and lets later PRs distinguish a +new decision from a new diagnostic. Expanding one string set cannot do this: +copying `result_kind` or an arbitrary host action into the quota action field +feeds different meanings into the same dispatch surface. Seeing an enum in a +comparison also does not prove that the system produces its values. M1 separates +these evidence roles and fixes the scoped fallback mismatch that recognized +unproduced `skip` while the actual output was `quota_skip`. + +The boundary is deliberately limited: the root action remains the distinguishable +`D ⊔ F` union without adding wire tags to every string; only load-bearing producers +require registration, not every consumer; historical signed data remains readable. +The checks cover finite vocabularies, supported output forms and generated artifact +consistency. They do not prove whole-program semantic completeness, reachability of +all branches or arbitrary variable-flow safety. + +The cost is regenerating bindings after owner changes and installing the locked +TypeScript parser for local scans. Reusing existing CI jobs still adds job work and +contributor repair effort; no new required job does not mean no new obligation. +First classify a failure: replace bare actions with owner references; accompany a +new decision with owner, producer and consumer validation; keep diagnostics in +`error_code` and Turn results in `decision`. Use Section 10 regeneration commands +for stale artifacts. Repair scanner false positives with a regression example, +rather than widening the vocabulary, reducing coverage or relaxing budgets. + +#### Output contracts and compatibility boundaries + +Let `D` be the 32 decision values owned by `EffectiveAction`, and `F` the four +values owned by `AgentScopeFrontierAction`. The root should-run and its envelope +projection retain the existing action strings through `A = D ⊔ F`. The registry +anchors the two member vocabularies and checks `D ∩ F = ∅`; hence the value +identifies its domain without a new wire tag. The TypeScript bindings and union +type derive from these owners, not an independently maintained third value list. +This is the registered-union option in Q6. A union arm cannot establish another +owner's producer liveness, and a canonical decision function's scalar return +domain remains `D`. + +| Surface | Current contract | Compatibility | +| --- | --- | --- | +| Root should-run / Turn Envelope `effective_action` | Decision/frontier union `A` | Frontier verdicts retain their meaning and spelling | +| Nested `agent_scope_frontier_v1.action` | Frontier domain `F`; one emitted action field | Readers prefer `action` and retain the old v0 alias as fallback | +| Internal journal replay observation | Existing `decision=replay_legal\|replay_blocked` | Public inspection and stored journal shapes do not change | +| Turn-result Effect observation | Turn verdict in `decision`; `effective_action=null` | Intentional projection change: read `decision` for the verdict; host action fields cannot author a quota decision | +| Action-selection rejection or deferral | `effective_action=quota_skip`; diagnostic in `error_code` | Intentional CLI change: readers distinguish reasons using the unchanged diagnostic code | + +New frontier writes remove the redundant nested `effective_action` and advance +the nested schema to v1. They do not normalize historical signed v0 documents: +the envelope capsule still preserves both legacy keys when present, and journal +resume returns the stored plan unchanged. Compatibility tests characterize old +signatures before the migration, mutate signed fields to prove coverage, and use +the real filesystem journal writer and resume reader. New v1 signatures change +only for the declared nested schema/field reduction. No frontend setting owns +this alias; the quota CLI and Markdown reader are covered by the live tests. + +The transient `effect.interpret_turn_result` projection previously copied either +an arbitrary host action or `result_kind` into the quota action field. It now +emits JSON null, preserved as `None` by the Python adapter. Its TypeScript return +type fixes the action to null; quota observations retain their existing string +action. The executor reads the result's `decision` and persists the normalized +host result and plan, not this transient observation. Real host validation still +rejects unsupported action fields, and executor/journal replay tests cover the +unchanged no-spend wait path. This projection change does not migrate stored +result, receipt or journal schema versions. + +The literal guard uses Python AST and the TypeScript compiler parser for bounded +field writes, comparisons, membership and match/switch cases. It rejects bare +action literals even when registered: import the owner instead. Conditions, +unrelated fields, comments and source examples inside strings do not count as +action values. This is a syntax boundary, not a whole-program data-flow proof; +dynamic keys, aliases and unresolved expressions retain their declared limits. +The generated bindings/glossary freshness check reuses the existing PR pytest +and smoke path; this milestone adds no required CI job. + ### Formal model and proof boundary The registry is a finite specification of a larger program semantics. Let @@ -396,7 +540,7 @@ The minimum semantic obligations are: These are different proof obligations. M0 establishes owner-set equality, cross-runtime parity, the declared executable projection, and inventory -freshness. Fixed literal forms and closed-set carriers provide bounded evidence, +computed from the current tracked tree. Fixed literal forms and closed-set carriers provide bounded evidence, not whole-program proof. M0.5 adds bounded producer and scope checks. Producer discovery over dynamic code, behavioural equivalence of `same_concept`, and persisted-reader compatibility remain unproved until their source-to-sink @@ -473,9 +617,10 @@ vocabulary key fails the smoke. | `vocabularies..tier`, `status` | `kernel`, `cross_runtime`, `cross_module`; `canonical`, `legacy`, `merge_candidate` | Closed enumerations | | `vocabularies..literal_scan` | `field`, roots, suffixes | Every literal the fixed dispatch forms capture is registered; every registered value is captured or variable-sourced (I2) | | `vocabularies..variable_sourced_values` | value to producer module | The producer still contains the quoted value | -| `vocabularies..scope` (M0.5) | `global` or `bounded_context`; a `bounded_context` entry lists `contexts`, each with one owner symbol | Closed enumeration; declared bounded-context names are excluded from `multi_value_forks`; an undeclared multi-module name stays a fork (I14) | -| `vocabularies..producers` (M0.5) | `path::Symbol` sites that write the field, required for `kernel` | Every site writes registered values only; every value not under `compatibility_only` has at least one site or a variable-sourced entry (I12, I13) | -| `vocabularies..compatibility_only` (M0.5) | values kept so readers of persisted records still resolve them | Subset of `values`; zero production sites; each carries a `value_notes` reason and a retirement milestone | +| `scope_declarations.` (M0.5a) | `bounded_context` and its context IDs, each with one `module::Symbol` owner | Every declared name resolves to one inventory fork, names every defining module exactly once, and is excluded only from `multi_value_forks_semantic`; undeclared forks remain visible (I14) | +| `vocabularies..input_producer` | Fixed executable decoder witness, currently `turn_result_kind` only | Every registered input produces the matching typed member and invalid probes reject; arbitrary callable selection is forbidden | +| `vocabularies..producers` (M0.5) | `path::Symbol` sites that write the field, required for `kernel` | Every site writes registered values only; every value not under `compatibility_only` has at least one source site or executable input witness (I12, I13) | +| `vocabularies..compatibility_only` (M0.5) | values retained for persisted readers or a legacy typed caller interface | Subset of `values`; zero production sites; each carries a `value_notes` reason and a retirement milestone | | `formal_model` | finite universes, role relations and hierarchy, semantic obligations, candidate decisions, and established/bounded/unknown/unproved claims | Exact schema, role hierarchy, candidate decisions, and invariant ids are checked by the drift smoke; enforcement stages cannot be mistaken for completed proofs | | `formal_model.enforcement_policy` | blocking-now, blocking-next, advisory, and unproved lanes | Every formal invariant appears exactly once and its lane agrees with its enforcement stage | | `vocabularies..value_notes`, `deprecated_values` | per-value review notes; values slated for removal | Names must be registered values | @@ -488,16 +633,17 @@ vocabulary key fails the smoke. | `dual_runtime_twins` | root and module budget | Tracked same-basename `.py`/`.ts` pair count is at or below budget; root and budget equal their code anchors (I5) | | `inventory_ratchets` | budgets for same-runtime fork names and definitions, conflicting names and definitions, schema-version forks, multi-value twins and forks, and the shared-vocabulary conflict and fork subsets | Inventory summary counts are at or below budget, and each budget equals its `BUDGET_ANCHOR` entry (I5, I9) | -`loopx/semantics/inventory_v0.json`, `schema_version` -`loopx_semantic_inventory_v0`, is generated by -`scripts/generate_semantic_inventory.py` and must equal a fresh build. It +The inventory retains `schema_version=loopx_semantic_inventory_v0`. +The guard builds it in memory from the complete tracked `loopx/` tree, once per +run, and uses that result for owner, scope and budget checks. No report file is +read. `scripts/generate_semantic_inventory.py` exports the same map on demand. It lists Python enums, closed sets, `Literal` aliases, TypeScript `as const` arrays, and duplicate definitions split into cross-runtime twins, same-runtime forks, conflicting values, and multi-value twins and forks, one entry per line. Every multi-value collision carries each defining module and its value set, so the divergence itself is reviewable rather than only its count. Consumer counts are printed by `--report`; merge-candidate groups are available -through `merge_candidate_groups` and not committed, so an -ordinary consumer edit does not touch the file; merge candidates are advisory +through `merge_candidate_groups`; all inventory output is uncommitted. +Merge candidates are advisory because an equal value set is not proof of one concept. Single-module string constants are counted, not listed. @@ -519,8 +665,8 @@ New vocabularies are added by a PR that adds the registry entry, raises the coverage floor, and, where a TypeScript owner exists, names its `as const` array. A vocabulary qualifies for curation when it is dispatched on by more than one module or crosses the Python/TypeScript boundary; everything else is -mapped by the inventory without curation. Adding any carrier regenerates the -inventory in the same PR. +mapped by the inventory without curation. Adding a carrier is discovered +on the next full-tree scan; genuine shared-contract changes still need review. ## 6. Alternatives and design choices @@ -533,7 +679,7 @@ inventory in the same PR. | Grep-based lint in CI without a registry | Encodes the allowed set in the linter, which becomes a second registry with no review trail. | | Extend `maintainability_ratchet.py` instead of a new registry | Its subject is module metrics and dependency direction with per-module ceilings; vocabulary shape needs values, owners, and relations. The two share the ratchet idea, not the data model. Merging exception lifecycles is Q7. | | Put the scan regex in the registry | A regex in data can be narrowed in the same edit that widens a vocabulary; the M0 review showed the first pattern missed every TypeScript `===` site. Forms are fixed in the smoke and the suffix set is floored. | -| Commit consumer counts in the inventory | Every consumer edit would churn the file and make the freshness check noise. Counts stay advisory via `--report`. | +| Commit the computed inventory or consumer counts | Structural churn adds merge conflicts without new authority. Compute the full tree and expose optional reports; consumer counts remain advisory. | ## 7. Safety, privacy, and compatibility @@ -541,7 +687,7 @@ inventory in the same PR. with the check present or absent. - The scanner uses `git ls-files --cached -z` and reads the indexed source paths from the working tree. Untracked and ignored files are excluded; stage a new - source path before regenerating the inventory. Tracked symlinks and invalid + source path before running the inventory scan. Tracked symlinks and invalid Python syntax fail closed. A checkout with Git metadata is required. - Literal and TypeScript carrier scans recognize both single and double quotes. They remain structural text scans, not complete parsers or data-flow analysis. @@ -573,14 +719,14 @@ inventory in the same PR. | Claim | Test or evidence | Required result | Boundary / exclusions | | --- | --- | --- | --- | | Registry and inventory match the code at baseline | `uv run --extra test loopx canary smoke-suite --script semantic-vocabulary-drift-smoke.py` | `ok` with coverage, ratchet, budget, and twin report | Proves parity for registered vocabularies and mapped carriers only | -| Inventory is fresh | `uv run python scripts/generate_semantic_inventory.py --check` | exit 0 | Structural map only | +| Inventory is computed on demand | `uv run python scripts/generate_semantic_inventory.py` | valid JSON on stdout, no repository writes | Full tracked tree, not only the PR diff | | Scanner classification rules | `uv run --extra test python -m pytest tests/architecture/test_semantic_inventory.py` | pass | Fixture repository; rules from this RFC, not from output | | A widened `effective_action` set fails closed in Python | Add an unregistered literal via `==`, membership, or conditional expression | Failure names the value and file | Mutation exercise; not a committed test | | A widened `effective_action` set fails closed in TypeScript | Add an unregistered literal via `===` or a ternary | Same | Same | -| A forked constant fails closed | Redefine `TURN_ENVELOPE_SCHEMA_VERSION` or `HANDOFF_MODES` in a non-owner module, regenerate the inventory | Failure lists the extra defining module or the fork budget | Same | +| A forked constant fails closed | Redefine `TURN_ENVELOPE_SCHEMA_VERSION` or `HANDOFF_MODES` in a non-owner module, run the smoke | Failure lists the extra defining module or the fork budget | Same | | Python and TypeScript owners cannot diverge | Remove one entry from a registered `as const` array, or widen a registered enum | Failure names the missing or unregistered value | Same | | The registry cannot be weakened by data alone | Declare a bare-module owner; drop an owner; narrow suffixes to `.py`; rename a vocabulary another relation references; add an unknown key | Each fails naming the rule | Same | -| A new carrier is visible | Add an enum without regenerating | Failure says the inventory is stale | Same | +| A new carrier is visible | Add a tracked enum without exporting a report | Current scan includes it; no freshness-only failure | A duplicate spanning changed and unchanged files still fails its budget | | Conflicting spellings cannot grow | Add a third value for an already-conflicting name, regenerate | Failure names the definitions budget | Same | | A multi-value collision cannot grow | Define one closed-set name in two modules with divergent values, or with equal values, and regenerate | `multi_value_forks` or `multi_value_twins` fails naming the new name | Mutation exercise; not a committed test | | The registry cannot relax its own ratchet | Lower any `coverage_floor` count, raise any `inventory_ratchets` budget, or raise a retirement budget, in the same diff that removes the coverage it counts | `COVERAGE_ANCHOR`, `BUDGET_ANCHOR`, or `RETIREMENT_ANCHOR` fails naming the anchored value | Mutation exercise; moving an anchor is a code edit a reviewer sees | @@ -590,12 +736,12 @@ inventory in the same PR. | Measurement covers both carrier shapes and filters local naming | `uv run --extra test python -m pytest tests/architecture/test_semantic_inventory.py` | pass, including the collision and module-local-convention fixtures | Rules come from this RFC, not from scanner output | | No behavior change from the two owner fixes | `uv run --extra test python -m pytest tests/test_loopx_turn_transaction.py tests/test_loop_turn_loop_controller.py tests/test_turn_loop_disposition.py tests/test_loopx_turn_managed_step.py tests/control_plane -k authority` and `uv run --extra test loopx canary premerge --from-git-diff` | pass | Environment failures already present on `main` are excluded when reproduced on a clean tree | | Docs governance accepts the RFC pair | `python3 examples/docs-governance-smoke.py` | pass | Checks mirror, links, index | -| Retirement budgets count substrings, not identifiers | `goal_boundary` counted with `in file.text` and with `\bgoal_boundary\b` | 35 vs 30 Python modules on the baseline | Known boundary; M3's zero-reader gate needs the identifier count, tracked in Section 12 | -| The module-local convention filter is a code edit | Widen `MODULE_LOCAL_CONVENTION` in `inventory.py` and regenerate | `*_semantic` budgets fall with no code change elsewhere | Known boundary; the regex is in code so the widening is a reviewed diff, and the unfiltered totals stay budgeted | +| Retirement budgets use standalone field tokens | `count_identifier_modules()` uses identifier boundaries for the six fields | `goal_boundary`: 30 Python modules under the new metric; the old substring metric was 35 | Conservative lexical measure; it removes compound-name false positives but does not prove semantic reader absence | +| The module-local convention filter is a code edit | Widen `MODULE_LOCAL_CONVENTION` in `inventory.py` and scan | `*_semantic` budgets fall with no code change elsewhere | Known boundary; the regex is in code so the widening is a reviewed diff, and the unfiltered totals stay budgeted | | A registered value nobody produces fails (M0.5) | Run the production-form scan on the baseline | Fails naming `effective_action` and `skip`; passes after `skip` is removed or listed `compatibility_only` | First expected I12 failure; a compared-only value is not carried | | A producer of an unregistered value fails (M0.5) | Write `effective_action: "brand_new"` in a listed producer site | Fails naming the site and the value even though no consumer compares it | I13; production is stricter than comparison | -| A bounded-context name leaves the fork budget only by declaration (M0.5) | Declare `SOURCE_SURFACES` with its four contexts; separately, rename one definition without declaring | The declaration lowers `multi_value_forks` to 3; the rename alone does not | I14; the honest fix is a registry edit a reviewer sees, the rename is code without registry change | -| An upstream merge can stale the committed inventory | Replay the scanner over the first parent and the merge of the last twenty `upstream/main` merge commits | 8 of 20 merges change at least one carrier | Measured cost of committing a snapshot; the handling rule is Section 10 and Section 12 Q9 | +| A bounded-context name leaves only the semantic fork budget by declaration (M0.5a) | Declare `SOURCE_SURFACES` with its four contexts; separately, rename one definition without declaring | Raw `multi_value_forks` stays 4, `multi_value_forks_semantic` is 3; a rename alone changes neither semantic accounting nor declaration | I14; the honest fix is a registry edit a reviewer sees, the rename is not a repair | +| Historical committed snapshots could become stale across merges | Replay the scanner over the first parent and the merge of the last twenty `upstream/main` merge commits | 8 of 20 merges change at least one carrier | Historical cost motivating Q9; current checks compute the combined tree without a committed snapshot | | The formal model cannot silently lose a proof obligation | Remove an invariant, role, relation, candidate decision, or proof-boundary category from `formal_model` | The drift smoke fails on the exact formal-model shape | The model is a finite contract and proof ledger; it does not prove the listed properties by itself | Known limits, stated so the check is not over-trusted: @@ -643,19 +789,23 @@ for a diff touching `loopx/control_plane/` alone, and the fleet workflow is deliberately not a PR-required check. A fleet-discovered smoke is not a commit-time check until a required PR job collects it. -**Merge-order hazard.** `inventory_v0.json` is a committed snapshot of the -whole `loopx/` tree, and the smoke fails when the tree and the snapshot differ. -Two pull requests that each add a carrier and each regenerate the inventory are -both green against the `main` they were built on; whichever merges second -leaves `main` with a snapshot missing the first one's entries, and the sweep on -`main` is red until someone regenerates. On the last twenty merges to -`upstream/main`, eight changed at least one carrier, so this is a weekly event, -not a corner case. The first upstream sync of this branch reproduced it: twelve -merged commits added one enum and three closed sets and the check failed -until regenerated. The handling rule is Section 12 Q9; until it is decided, the -rule is that the person who merges a PR after a red `main` regenerates the -inventory in a follow-up commit that touches only `inventory_v0.json`, and the -smoke's failure text names that command. +**On-demand inventory (Q9).** The former committed snapshot imposed a second +synchronization obligation on otherwise valid PRs. It is removed. Let `f(T)` be +the full tracked-tree inventory and `G(f(T), R)` the existing registry, owner, +scope and budget predicates. Checks still evaluate `G(f(T), R)`; only the extra +condition `I_committed = f(T)` disappears. The same computed map feeds the +checks, so stale or missing local reports cannot hide a new fork. This does not +prove that independently valid branches cannot introduce a semantic conflict +when combined: validate the combined tree normally. Never replace the full-tree +scan with a diff-only scan. + +For inspection, run `uv run python scripts/generate_semantic_inventory.py` for +JSON stdout or append `--output .local/semantic-inventory.json` for an optional +report. `--output --check` compares that explicit report without writing; +`--check` alone fails with migration guidance. Reports may be attached to CI +artifacts, but are neither committed nor required to run semantic checks. +Bindings and the glossary remain committed generated contracts with freshness +checks; this decision concerns only the repository census. No new CI job is added. **Interpreter and checkout.** Run the commands above from the target worktree with `uv run`; Python compatibility comes from `pyproject.toml` (`>=3.11`), @@ -666,12 +816,26 @@ is compatible. See [local validation](../../development/testing-and-quality.md#l for setup, interpreter/source readback, and lockfile boundaries. Historical receipts below retain the commands actually executed. +For an already provisioned environment, `bash scripts/loopx-python.sh --exec +` selects a compatible installed interpreter, including +`.venv/bin/python`, or honors `LOOPX_PYTHON`. That selector installs neither +Python nor dependencies. Fleet and premerge child commands can retain `python3` +because the selected project or CI environment supplies it on `PATH`. + +The TypeScript effective-action binding and the [glossary](../../reference/glossary.md) +are generated with `uv run python scripts/generate_semantic_bindings.py`. +Run it after changing the Python owner or registry. Carrier changes are scanned +automatically; exporting an inventory report is optional. The existing drift smoke and PR pytest sweep check freshness; +no additional required CI job is introduced. Install the locked Node dependencies +with `npm ci --ignore-scripts` before running the TypeScript production scan. + ## 11. Normative delivery plan | Milestone | Shipped behavior | Entry gate | Exit evidence | Rollback | | --- | --- | --- | --- | --- | -| M0 | Registry with 26 vocabularies and 9 relations, generated inventory with `--check`, drift smoke with fixed dispatch forms and coverage floor, two owner forks removed, RFC index entry | This RFC opened | Section 9 rows green; 20 mutation classes fail closed | Delete the smoke, `loopx/semantics/`, the generator, and its test | -| M0.5 | `scope` with `global` and `bounded_context` and per-context owners; `producers` and `compatibility_only` on `kernel` vocabularies; production-form scan with the two role checks (I12, I13); retirement budgets counted by identifier with all six anchors lowered in one diff (Q11); merge-order rule from Q9 written into Section 10 | M0 merged; Q9 decided or its interim rule accepted | Smoke green with I11 to I14 enforced; `skip` resolved; `multi_value_forks` at 3 by declaration; Section 9 role rows green; `turn_route` persistence answered for Q2 | Remove the three fields and the role checks; budgets return to the M0 anchors | +| M0 | Registry with 26 vocabularies and 9 relations, computed inventory with optional export, drift smoke with fixed dispatch forms and coverage floor, two owner forks removed, RFC index entry | This RFC opened | Section 9 rows green; 20 mutation classes fail closed | Delete the smoke, `loopx/semantics/`, the generator, and its test | +| M0.5a | `scope_declarations` with `bounded_context` and per-context owners; semantic fork count separated from raw inventory count | M0 merged | Smoke checks every declared context owner; raw `multi_value_forks` remains 4 and `multi_value_forks_semantic` is 3; undeclared forks still fail the budget | Remove the scope declarations and semantic-fork budget | +| M0.5b | `producers` and `compatibility_only` on `kernel` vocabularies; production-form scan with the two role checks (I12, I13); retirement budgets counted by identifier with all six anchors lowered in one diff (Q11); merge-order rule from Q9 written into Section 10 | M0.5a complete; Q9 decided or its interim rule accepted | Smoke green with I11 to I14 enforced; `skip` resolved; Section 9 producer rows green; `turn_route` persistence answered for Q2 | Remove producer fields and role checks; budgets return to the pre-M0.5b anchors | | M1 | `EffectiveAction` typed enum in one owner module; the replay observation and frontier slots split off (Q6); producers and consumers import it; registry `literal_scan` tightened to the enum | M0.5 merged; owner module chosen (Q3); slot split decided (Q6) | Smoke green; zero bare `effective_action` literals outside the owner; parity fixtures for status/should-run unchanged | Revert to literals; registry keeps the set | | M2 | Route-to-disposition projection, the `decide_loop_disposition` decision table, and the cross-runtime sets published through a shared contract with generated Python and TypeScript bindings, following the coordination contract generator | M1 merged; Q2 and Q7 decided | Generator `--check` and smoke green; `settlement.ts` and `transaction.py` read the generated set | Regenerate from prior contract | | M3 | Per-field retirement of legacy should-run fields, one field per PR, budgets lowered to zero and the field removed | Field has zero external readers proven by producer/reader research | Schema-reduction record per `AGENTS.md`; Appendix B entry | Restore field from the last writer | @@ -684,7 +848,7 @@ vocabulary property the smoke can check. Rows marked *open* wait on a Section | Surface | Baseline (`1dc6ad8d8`) | Target when this RFC closes | Reached by | | --- | --- | --- | --- | -| `effective_action` values | 33 literals, no owner symbol | one enum owner; `skip`, `observe_replay`, `block_replay`, and the two `quota_action_selection_*` codes gone from the decision slot; about 28 values | M1 | +| `effective_action` values | 33 literals, no owner symbol | one enum owner; `skip`, `observe_replay`, `block_replay`, and the two `quota_action_selection_*` codes gone from the decision slot; 32 decision values after accounting for the five previously missed producers and retiring the synthetic operator_gate value | M1 | | `effective_action` slots in one envelope | 3 vocabularies under one field name | 1, or a registered union if Q6 keeps the field | M1 (Q6) | | Turn vocabularies | 3 sets, 28 values, 21 distinct, 7 redundant spellings | 3 sets kept; projection and decision table generated and checked; spellings unchanged unless Q10 sets a merge | M2 (Q2, Q10 *open*) | | Same-runtime forks, semantic | 18 names | 0 | baseline PRs | @@ -774,34 +938,37 @@ introduce a competing target state. `wait`), and `stop`, `terminal`, `contract_error` exist on one side only. The `same_concept` relations record the four shared verdicts. Recommendation: keep both, publish the projection in M2, revisit after the - managed-step consumer matures. Needed before M2. The stated reason for - keeping both is that merging would touch persisted Turn records; that - premise is unverified. Before deciding, the M0.5 production-form scan (I12, - Section 5) applied to `turn_route` should establish - whether `turn_route` is ever written to the journal or a receipt, or only - flows in-process; if the latter, the cost of a merge is far lower than this - RFC assumes and Q10 applies. -3. **Owner module for `EffectiveAction`.** The registry declares no owner - today because no symbol exists; the literal scan is the only check. - Options: `quota/should_run_packet.py` (largest producer), a new - `quota/effective_action.py`, or the TypeScript `turn_envelope.ts` with a - Python import per the migration RFC. Recommendation: TypeScript owner with - generated Python binding only if M2 lands first; otherwise - `quota/effective_action.py`. Needed before M1. -4. **Companion glossary.** Whether to add `docs/reference/glossary.md` - generated from the registry `meaning` fields and the inventory. Owner: docs - maintainers. Recommendation: yes, in M1, generated so it cannot drift. + managed-step consumer matures. The persistence premise is now established: + `run_loopx_turn_once` writes `plan: dict(plan)` through the TypeScript journal + writer, including `plan.route.kind`; `load_loopx_turn_plan_from_journal` + restores that route. The executor replay regression checks an actual journal + on disk and the resume reader. Keep the three vocabularies and publish the + non-injective projection in M2; any later renaming needs a persisted-plan + migration, not just an in-process enum refactor. This evidence does not prove + compatibility of every external reader or every other persisted field. +3. **Owner module for `EffectiveAction`.** The implementation uses + `quota/effective_action.py`, matching the pre-generation option. Its runtime + callers serialize `.value` to preserve existing strings. TypeScript consumers + import `quota/effective_action.generated.ts`, generated from that enum with + member/value parity checked. M2 may generate both bindings from the shared + contract, retaining the existing import paths. No + second independent value list may be introduced into a runtime module. +4. **Companion glossary.** `docs/reference/glossary.md` is generated from the + registry's curated meaning, owner, value and compatibility metadata. It covers + registered vocabularies; the inventory remains the wider structural map. + The existing smoke rejects stale output. Edit the owner/registry and regenerate + rather than maintaining a second prose authority. Owner: docs maintainers. 5. **Term-family naming rule.** Whether new identifiers in the `gate`, `scope`, `packet`, `handoff`, `settlement` families must cite a glossary row in review. This is a review rule, not a smoke; recommendation is to adopt it in the first-review roster once the glossary exists. -6. **Split the three `effective_action` slots.** The decision slot, the - `agent_scope_frontier` slot, and the replay observation slot share one field - name in one Turn Envelope and carry three vocabularies; `skip` is compared - but never produced. Options: rename the observation and frontier slots, - or keep one field with a registered union. Owner: Turn Envelope owner. - Recommendation: rename in M1 so the enum in Q3 has one meaning. Needed - before M1. +6. **Action slot decision (Q6).** Keep the root should-run/Turn Envelope field + as the anchored, disjoint decision/frontier union. Nested frontier v1 uses + its existing `action`; journal replay uses its existing `observation.decision`. + No redundant replacement field is introduced. Preserve historical v0 signed + capsule fields on read; new writes use the versioned reduced shape. See the + M1 compatibility table and tests above. This decision does not authorize + unrelated legacy-field retirement or Turn outcome enum merging. 7. **Relation to `maintainability_ratchet.py`.** Whether the inventory ratchets adopt its reviewed exception lifecycle (`retirement_plan`, stale exception detection) or stay plain budgets. Recommendation: adopt it in M2 @@ -811,30 +978,23 @@ introduce a competing target state. with three or more external consumer modules or a cross-runtime twin must be curated. Recommendation: yes as a review rule now, enforced by the smoke only after a quarter of inventory history exists. Owner: kernel maintainers. -9. **Inventory freshness across merges.** The committed snapshot goes stale - when two carrier-adding PRs merge in sequence (Section 10, eight of the last - twenty upstream merges). Options: (a) branch protection requires the PR to - be up to date with `main`, which removes the hazard and slows every PR; - (b) the merger owns a regenerate-only follow-up commit, which keeps the - snapshot in git history and accepts a red `main` for minutes; (c) the - inventory is not committed and CI generates it for the PR diff only, which - loses `git blame` on carriers. Recommendation: (b) now, (a) if red `main` - exceeds once a week. Owner: repository maintainers. This is an operations - decision, not a code change; it belongs in the tracking issue's decision - list, not its task list. +9. **Inventory freshness across merges (Q9).** Adopt on-demand full-tree + computation and optional untracked reports. Retire the committed census and + its equality obligation; preserve semantic predicates, roots, floors and + budgets. This supersedes regenerate-after-merge and explicitly rejects the + old option's diff-only scan. Section 10 defines commands and proof limits. 10. **Target state for the Turn vocabularies.** Section 11's target table keeps three sets and seven redundant spellings by default because Q2 - recommends keeping both. If the M0.5 production-form scan in Q2 shows `turn_route` is - not persisted, the maintainers should choose between (a) three sets with a - generated projection, the current plan, and (b) a two-phase merge (dual- - write, then retire) to one spelling per concept. Without this decision the - RFC has budgets but no definition of done for its headline problem. + recommends keeping both. Q2's writer/readback evidence shows that `turn_route` + is persisted. The implementation therefore retains three distinct value sets + and generates their projection; it does not merge spellings. A future proposal + to merge them must provide a dual-read/versioned migration and reader proof. Owner: Turn driver owner. Needed before M2 closes. -11. **Retirement budgets by identifier.** The six legacy-field budgets count - `field in file.text`; `goal_boundary` matches `goal_boundary_repair`. M3's - zero-external-reader gate needs word-boundary counting, which lowers all six - anchors in one diff. Recommendation: do it before the first M3 PR. - Owner: kernel maintainers. +11. **Retirement budgets by identifier.** The six legacy-field budgets now use + `count_identifier_modules()`, so `goal_boundary_repair` is not counted as + `goal_boundary`. This is a conservative lexical metric, not proof of zero + semantic readers; computed accesses remain an evidence gap. Owner: kernel + maintainers. ## Appendix A: Execution ledger (non-normative) @@ -997,7 +1157,7 @@ introduce a competing target state. | Date | Decision | Owner / approval | Alternatives | Normative sections changed | | --- | --- | --- | --- | --- | -| — | none recorded | — | — | — | +| 2026-09-16 | Q9: compute the full inventory on demand; retire the committed census | Implementation for [maintainer feedback](https://github.com/huangruiteng/loopx/pull/4360#issuecomment-5692062394); PR review pending | Committed snapshot with post-merge regeneration; diff-only scan rejected | 1, I6, 3, 5, 9, 10, 12 | ## Appendix C: Evidence registry diff --git a/docs/architecture/rfcs/semantic-vocabulary-convergence-v0.zh-CN.md b/docs/architecture/rfcs/semantic-vocabulary-convergence-v0.zh-CN.md index 7512c2f1bd..febd02df6f 100644 --- a/docs/architecture/rfcs/semantic-vocabulary-convergence-v0.zh-CN.md +++ b/docs/architecture/rfcs/semantic-vocabulary-convergence-v0.zh-CN.md @@ -1,13 +1,13 @@ # RFC:语义词表收敛与提交期漂移检查(v0) - **RFC status:** Draft -- **Delivery maturity:** Partial(M0 的注册表、生成清单与漂移 smoke 随本 RFC 一起交付) +- **Delivery maturity:** Partial(M0 的注册表、计算清单与漂移 smoke 随本 RFC 一起交付) - **Authors / owners:** LoopX 贡献者;控制面内核维护者拥有批准权 - **Created:** 2026-09-15 -- **Last normative revision:** 2026-09-15 +- **Last normative revision:** 2026-09-16 - **Implementation baseline:** `1dc6ad8d8` - **Related contracts:** `loopx/semantics/vocabulary_v0.json`、 - `loopx/semantics/inventory_v0.json`、 + `loopx/semantics/inventory.py`、 `loopx/control_plane/turn_transaction_contract.json`、 `loopx/control_plane/coordination/coordination_state_contract_v0.json`、 [Turn Envelope v0](../../reference/protocols/turn-envelope-v0.md)、 @@ -31,25 +31,25 @@ RFC 成熟度与交付成熟度彼此独立。带日期的进度条目不修改 ## 1. 决策摘要 -1. **什么成为权威。** `loopx/semantics/` 下的两份文件。策展注册表 +1. **什么成为权威。** `loopx/semantics/` 下的策展注册表与检查时计算的清单。注册表 `vocabulary_v0.json` 为每个内核与跨运行时词表命名:允许定义它的确切 `module::Symbol`、词表之间的关系(同一概念、共享字段名、子集)、完整投影, - 以及仓库同意只降不升的预算。生成清单 `inventory_v0.json` 映射 `loopx/` 下 + 以及仓库同意只降不升的预算。计算得到的清单映射 `loopx/` 下 每一个闭集载体:字符串枚举、`Literal` 别名、命名闭集、TypeScript `as const` 数组,以及在多个模块中定义的每个常量名。一支公共 smoke `examples/semantic-vocabulary-drift-smoke.py` 在每个 PR 的默认 `pytest` 扫描 - 里用两份文件核对代码;premerge 与 full-public 舰队是附加表面(第 10 节)。 - 任何扩宽词表、分叉常量、新增载体或削弱注册表的 - 改动,必须在同一个 diff 里修改注册表或重新生成清单,评审者因此能把语义 - 变化当作变化看见。 -2. **什么不变。** 运行时行为、线上格式、枚举类本身。每个枚举继续住在自己的 - owner 模块里;注册表通过 AST 与文本扫描核对代码,不生成代码,产品代码也 - 永不导入它。 + 里用注册表和当前源码的扫描结果核对代码;premerge 与 full-public 舰队是附加表面(第 10 节)。 + 扩宽词表、分叉常量或调整预算须在同一 diff 中包含必要的 owner/注册表 + 修改。普通新增载体由扫描器自动发现,无须提交生成快照(Q9)。 +2. **权威与生成。** 每个枚举住在自己的 owner 模块里;注册表通过 AST 与文本 + 扫描核对代码,产品代码永不导入它。M1 生成器核对注册表一致性后,从 Python + owner 派生 TypeScript effective-action 绑定。这不改变线上取值,也不把值的 + 定义权转移给注册表;M2 的共享契约生成仍属于后续里程碑。 3. **默认与可选边界。** 检查对仓库始终开启。它没有运行时开关,因为它从不在 产品内运行。 4. **主要约束。** 失败即关闭、确定性、且不能仅靠改数据被削弱。任一运行时的 未注册字面量、第二个定义模块、预算超支、注册表列出但无模块携带的值、过期 - 的清单、不带符号的 owner 声明、低于记录下限的覆盖计数,每一项都让 smoke + 的生成绑定、不带符号的 owner 声明、低于记录下限的覆盖计数,每一项都让 smoke 失败。扫描识别的分发形式写在 smoke 里而不在注册表里。smoke 只读已跟踪 源码,不打印任何私有数据。 5. **本 RFC 不批准的事。** 把三套 Turn 结果枚举合并为一套、拆分 @@ -147,8 +147,8 @@ todos、capabilities 与 TypeScript 运行时各自拥有同一想法的一种 模式,但有一处刻意的不同:注册表的值必须**等于**锚点。先例用 `<=` 比较, 这会让一个已收紧到锚点以下的预算,在之后的 PR 里不改任何代码就涨回锚点。 相等性让每次收紧都是两个文件的 diff,每次放松都是评审者可见的代码修改。 -- **I6 同 diff 可见。** 语义变化与其注册表修改或清单再生成落在同一个可评审 - diff 里。 +- **I6 同 diff 可见。** 语义变化与必要的 owner、注册表或预算修改落在同一个可评审 + diff 中;计算得到的清单报告是证据,不是需提交的权威。 - **I7 确定性且公开安全。** 检查只读已跟踪源码,不需网络或凭据,失败文本只 命名文件与值,绝不含私有数据。 - **I8 覆盖只增。** 注册词表数、owner 符号数、投影数、关系数、schema 版本数 @@ -171,8 +171,9 @@ todos、capabilities 与 TypeScript 运行时各自拥有同一想法的一种 比较、序列化或展示一个值不带来任何所有权。开始写入值的解释者或透传者已经 变成生产者,必须登记为生产者。自 M0.5 起强制。 - **I12 每个内核值都被生产。** 对 `kernel` 词表,未列入 `compatibility_only` - 的每个值至少有一个固定生产形式能识别的生产位点,或一条 - `variable_sourced_values`。只被比较的值是死值或兼容值,绝不是 canonical。 + 的每个值至少有一个固定生产形式能识别的生产位点,或已登记输入解码器的 + 可执行见证。变量来源备注本身不能作为生产证据。只被比较的值是死值或兼容值, + 绝不是 canonical。 `effective_action` 的 `skip` 是第一个预期失败。自 M0.5 起强制;M0 的字面量 扫描把被比较的值当作已携带。 - **I13 生产者只写注册值。** 写入注册集合之外值的生产位点失败即关闭,与是否 @@ -189,7 +190,7 @@ todos、capabilities 与 TypeScript 运行时各自拥有同一想法的一种 ### 范围内 - 注册表文件、其 schema,以及编辑它的所有权规则。 -- 生成清单、带 `--check` 的生成器及其单元测试。 +- 计算清单、可选报告的导出/校验命令及其测试。 - 漂移 smoke 及其在 premerge 与 full-public 舰队中的位置。 - M0 注册的词表:四个 Turn 内核集合(`turn_result_kind`、`turn_route`、 `loop_disposition`、`effective_action`)、`agent_scope_frontier_action` 与 @@ -260,9 +261,9 @@ todos、capabilities 与 TypeScript 运行时各自拥有同一想法的一种 个案例:它在 `global_risks.py`、`global_todos.py`、`summary_all.py`、 `pr_review.py` 的四处定义各自列出那一个 CLI 命令的数据来源,值集本来就该不 同。它今天被计入 `multi_value_forks`,且不得用改名来"修",因为改名只让数字 -下降、不改变代码含义。M0.5 给注册表加 `scope` 字段,至少含 `global` 与 -`bounded_context`,允许一个有界上下文名字连同其所属上下文声明一次,并把已 -声明的名字从分叉预算移出(I14、下方 schema 表与第 11 节的 M0.5 行)。在此之 +下降、不改变代码含义。M0.5 的作用域子阶段增加顶层 `scope_declarations`,至少支持 `global` 与 +`bounded_context`;有界上下文名字只声明一次并列出其 owner,同时从语义分叉预算 +中移除,原始清单计数仍保留(I14、下方 schema 表与第 11 节的 M0.5 行)。在此之 前分叉预算是一个包含这一处已知误分类的上 限,记在注册表 `inventory_ratchets` 的备注里。 @@ -296,6 +297,119 @@ M0.5 之后新增或删除值时;`cross_module` 只在晋升后(Q8)。持 答的属性:若某个已列生产者符号是 journal 或 receipt 的写方,该词表标为 `persisted`,这正是 Q2 与 Q10 等待的事实。 +### M0.5/M1 的可执行生产证据 + +生产者守卫与 owner 载体检查使用不同证据。定义枚举成员只能证明集合成员关系, +不能证明生产。对带生产者元数据的词表,守卫比较观察到的结果值与 `values`,拒绝 +未登记的**函数位点**,并要求每个非兼容值存在观察到的生产者。变量来源备注不能 +替代存活证据。`return_producers` 列出其标量返回表达式属于该词表的已登记函数; +返回整个 packet 的构建器不会因此把无关返回文字当作词表值。 + +Python 通过 AST 解析字段赋值(含下标、属性及带注解赋值)、字典、调用关键字、 +owner 成员结果及声明函数的标量返回。导入枚举的别名只解析到已登记 owner; +被遮蔽的名字、重复赋值及未解析调用仍为 unknown。条件表达式只检查结果分支, +排除条件中的字面量。TypeScript 的对象写入、赋值及声明返回使用仓库的 TypeScript +解析器。两个解析器都不执行被检查源码。这些是句法结果证据,不是可达性或全程序 +数据流证明。 + +`uv run python examples/semantic-vocabulary-drift-smoke.py --report` 列出未解析的生产 +位置。unknown 不能补足缺失值的生产证据。生产者守卫对六个 kernel 条目使用不同证据:`effective_action`、`turn_route`、 +`loop_disposition` 和 `agent_scope_frontier_action` 使用源码见证; +`turn_result_kind` 另有固定入口 `transaction._result_kind` 的可执行输入见证。 +真实解码器必须为每个注册输入返回相同的类型化成员,并拒绝非法探测输入。这证明 +存在允许的生产路径,不表示 Host 实际发出过全部成员或所有 Host 执行都合法。 +`input_producer` 不能从数据任意指定执行代码,验证入口固定在 smoke 中。 + +`lease_action` 明确分类为 legacy/兼容保留:仓库运行时调用者使用分开的 +acquire/renew/transfer/release command 类。四个成员为旧的类型化 +`LeaseModeGateCommand` 输入接口保留到 M4 调用者/迁移评审;不声称存在持久化 +使用。只有每个值都带保留理由及退休里程碑时,生产者列表才能为空。新发现的 +生产者必须让原兼容声明失败。 +没有生产者元数据的 kernel 词表会明确 +报告为覆盖待完成,不能把 owner 一致性宣称为 I12/I13 完成。所有要求的词表通过 +相应验收行之前,M0.5 仍未完成。 + +decision owner 补登记了旧字面量扫描漏掉的五个现存结果:`blocked_health`、 +`blocked_wait`、`control_plane_repair`、`operator_gate_notify` 和 `throttled_skip`。 +这些登记保留现有 quota 行为。M1 删除无生产者的 `skip` 和合成夹具使用的 +`operator_gate`。fallback 消费者改为识别实际的 `quota_skip`;允许执行的 scoped +fallback 不能仍携带跳过动作。旧字段退休仍须单独验收。 + +TypeScript 解析器准备命令是在仓库根目录执行 `npm ci --ignore-scripts`,使用仓库 +锁文件。扫描本身不需网络或凭据;需要 Python 3.11+ 和仓库支持的 Node 运行时。 + +### M1 动作值域与兼容性 + +#### 为什么需要这一阶段 + +M1 的目标是让调用者读对字段,并使后续 PR 能区分“新增决策”与“新增诊断”。 +仅登记一个更大的字符串集合不能解决这个问题:把 `result_kind` 或任意 host +动作复制进 quota 动作字段,会让下游将不同含义的值送入同一分发逻辑;仅看到 +枚举被比较,也不能证明系统确实产生过该值。M1 分离这些证据,并修复 scoped +fallback 仍识别无生产者 `skip`、而实际输出为 `quota_skip` 的不一致。 + +这里采用最小必要边界:根动作仍是可区分的 `D ⊔ F`,不为所有字符串增加 +线上标签;只登记承重生产者,不要求每个消费者登记;保留历史签名数据的读取 +契约。它能检查有限词表、已支持输出形态和生成物一致性,不能证明整个程序 +语义完备、所有分支可达或任意变量流都安全。 + +代价是改动 owner 后要再生成绑定,本地扫描还需锁定的 TypeScript 解析器。这些检查复用已有 CI 作业,但仍会增加 +作业工作量和提交修复成本;“没有新增 required job”不等于没有新增义务。 +失败时先判断是否真的改变语义:裸动作值改为 owner 引用;新增决策补 owner、 +生产者和消费者验证;诊断留在 `error_code`,Turn 结果留在 `decision`;生成物 +过期才运行第 10 节命令。扫描误判应修扫描规则并加反例,不能通过扩宽值集、 +降低覆盖或放宽预算来消除失败。 + +#### 输出契约与兼容边界 + +生产证据中的 `return_paths` 指定返回对象的明确字段/索引路径;`call_producers` +登记经过评审的 builder 输出参数,并核对已跟踪源码中的模块绑定和真实签名。 +声明与代码锚点同步修改。这是经过评审的输出契约,不是对任意 helper 函数体 +语义的自动证明。局部枚举容器和任意判定函数的参数不证明生产;选出的标量必须 +流向已观测输出。被修改或逸出的可变别名保持 unknown。代码生成采用严格枚举 +提取;包括第二个 owner 无效的情况在内,都先拒绝不支持的成员,再写任何生成物。 +selector 必须与代码拥有的映射精确相等,其他词表默认为空,因此新增 selector +也必须修改代码锚点。同名 keyword 参数在输出角色未确认时,只保留值域/unknown +证据,不能证明生产者存活,也不能迫使普通消费者登记为生产者。 + +令 `D` 为 `EffectiveAction` 拥有的 32 个决策值,`F` 为 +`AgentScopeFrontierAction` 拥有的四个前沿值。根 should-run 及其 envelope 投射 +通过 `A = D ⊔ F` 保留现有动作字符串。注册表以代码锚点固定两个成员词表,检查 +`D ∩ F = ∅`,因此无需新增线上标签即可从值识别所属域。TypeScript 绑定和联合 +类型从两个 owner 派生,不另维护第三份值表。这是 Q6 的注册并集方案。一个 +union 成员不能证明另一个 owner 的生产者存活性;规范决策函数的标量返回域 +仍为 `D`。 + +| 表面 | 当前契约 | 兼容性 | +| --- | --- | --- | +| 根 should-run / Turn Envelope `effective_action` | 决策/前沿并集 `A` | 保留前沿判决的语义与拼写 | +| 嵌套 `agent_scope_frontier_v1.action` | 前沿域 `F`,只输出一个动作字段 | 读者优先读 `action`,保留旧 v0 别名兜底 | +| 内部 journal replay observation | 使用既有 `decision=replay_legal\|replay_blocked` | 公共 inspection 与落盘 journal 形状不变 | +| Turn-result Effect observation | Turn 判决放在 `decision`,`effective_action=null` | 有意的投射变化:通过 `decision` 读取判决,host action 字段不能生成 quota 决策 | +| 动作选择拒绝或延迟 | `effective_action=quota_skip`,诊断放在 `error_code` | 有意的 CLI 变化:通过不变的诊断码区分原因 | + +新 frontier 写入删除嵌套的冗余 `effective_action`,并将嵌套 schema 升为 v1。 +历史已签名 v0 文档不在读取时归一化:envelope capsule 保留当时存在的两个旧 +键,journal 恢复原样返回落盘 plan。兼容测试在迁移前刻画旧签名,再逐字段突变 +验证签名覆盖,且使用真实文件 journal 写入者与恢复读者。新 v1 签名只因声明的 +嵌套 schema/字段缩减发生变化。该别名没有前端配置 owner;真实 quota CLI 和 +Markdown 展示已纳入测试。 + +瞬时 `effect.interpret_turn_result` 投射以前把任意 host action 或 `result_kind` +复制到 quota 动作字段,现在输出 JSON null,由 Python 适配为 `None`。 +TypeScript 返回类型将该动作固定为 null;quota observation 仍保留原有字符串 +动作。executor 读取结果的 `decision`,持久化规范化后的 host result 和 plan, +不持久化这份瞬时 observation。真实 host 校验仍拒绝不支持的 action 字段; +executor/journal 重放测试验证了不变的无消费 wait 路径。该投射变化不迁移落盘 +result、receipt 或 journal 的 schema 版本。 + +字面量守卫使用 Python AST 与 TypeScript 编译器解析器识别有界的字段写入、 +比较、成员测试和 match/switch。即便值已注册,裸动作字面量也会失败,必须导入 +owner。条件表达式、相邻的其他字段、注释和字符串中的源码样例不计作动作值。 +这是句法边界,不是全程序数据流证明;动态键、别名与未解析表达式仍受明确的 +能力边界限制。绑定/术语表的新鲜度检查复用现有 PR pytest 与 smoke,不新增 +required CI job。 + ### 形式模型与证明边界 注册表是更大程序语义的有限规格。令 `V` 为已注册词表集合,`L` 为源码位点集合, @@ -323,7 +437,7 @@ R ⊆ L × V × Version 将值持久化 6. **持久化兼容性:** 持久化词表改变时,必须保持所有读者可读,或声明带版本的迁移。 这些是不同的证明义务。M0 已建立 owner 集合相等、跨运行时 parity、声明的可执行投影 -和 inventory 新鲜度。固定字面量形式与闭集载体只提供有界证据,不是全程序证明。M0.5 +和基于当前已跟踪源码树计算的清单。固定字面量形式与闭集载体只提供有界证据,不是全程序证明。M0.5 增加有界的生产者和作用域检查。动态代码中的完整生产者发现、`same_concept` 的行为等价、 以及持久化读者兼容性,在建模源码到结果的边之前仍然是未证明状态。注册表通过 `formal_model` 保存这条证明边界;标记为 `unproved` 的性质是显式局限,不能被当作默认通过。 @@ -387,9 +501,10 @@ external_input | compatibility_only | unknown | `vocabularies..tier`、`status` | `kernel`、`cross_runtime`、`cross_module`;`canonical`、`legacy`、`merge_candidate` | 封闭枚举 | | `vocabularies..literal_scan` | `field`、根目录、后缀 | 固定分发形式捕获的每个字面量都已注册;每个注册值被捕获或来自变量(I2) | | `vocabularies..variable_sourced_values` | 值到生产者模块 | 生产者仍包含带引号的该值 | -| `vocabularies..scope`(M0.5) | `global` 或 `bounded_context`;`bounded_context` 条目列出 `contexts`,每个含一个 owner 符号 | 封闭枚举;已声明的有界上下文名字从 `multi_value_forks` 排除;未声明的多模块名字仍是分叉(I14) | -| `vocabularies..producers`(M0.5) | 写入该字段的 `path::Symbol` 位点,`kernel` 必填 | 每个位点只写注册值;未列入 `compatibility_only` 的每个值至少有一个位点或一条变量来源条目(I12、I13) | -| `vocabularies..compatibility_only`(M0.5) | 为让已持久化记录的读者仍能解析而保留的值 | `values` 的子集;零生产位点;每个值带 `value_notes` 理由与退休里程碑 | +| `scope_declarations.`(M0.5a) | `bounded_context` 及上下文 ID,每个上下文含一个 `module::Symbol` owner | 每个声明名对应一个 inventory 分叉,并且一次且仅一次列出全部定义模块;只从 `multi_value_forks_semantic` 排除,未声明分叉仍可见(I14) | +| `vocabularies..input_producer` | 固定的可执行解码入口,目前仅用于 `turn_result_kind` | 每个注册输入必须产生匹配的类型化成员,非法探测输入必须拒绝;禁止任意选择执行入口 | +| `vocabularies..producers`(M0.5) | 写入该字段的 `path::Symbol` 位点,`kernel` 必填 | 每个位点只写注册值;未列入 `compatibility_only` 的每个值至少有一个源码生产位点或可执行输入见证(I12、I13) | +| `vocabularies..compatibility_only`(M0.5) | 为持久化读者或旧类型化调用接口保留的值 | `values` 的子集;零生产位点;每个值带 `value_notes` 理由与退休里程碑 | | `formal_model` | 有限的集合、角色关系与层次、语义义务、候选决策,以及已建立/有界/unknown/未证明的声明 | 漂移 smoke 校验精确 schema、角色层次、候选决策和不变量 ID;属性实施阶段不能冒充已完成证明 | | `formal_model.enforcement_policy` | 当前阻断、下一阶段阻断、建议性和未证明层级 | 每个形式不变量恰好出现一次,且层级与其实施阶段一致 | | `vocabularies..value_notes`、`deprecated_values` | 逐值评审备注;计划删除的值 | 名字必须是已注册值 | @@ -402,13 +517,14 @@ external_input | compatibility_only | unknown | `dual_runtime_twins` | 根目录与模块预算 | 同名 `.py`/`.ts` 对数不超过预算(I5) | | `inventory_ratchets` | 同运行时分叉的名字数与定义数、冲突的名字数与定义数、schema 版本分叉数、多值孪生与分叉数,以及共享词表冲突与分叉子集的预算 | 清单摘要计数不超过预算,且每个预算必须等于其 `BUDGET_ANCHOR` 条目(I5、I9) | -`loopx/semantics/inventory_v0.json`,`schema_version` 为 -`loopx_semantic_inventory_v0`,由 `scripts/generate_semantic_inventory.py` 生成, -必须与新鲜构建完全一致。它每行一条地列出 Python 枚举、闭集、`Literal` 别名、 +清单保留 `schema_version=loopx_semantic_inventory_v0`。守卫每次从完整的 +已跟踪 `loopx/` 源码树计算一次,在 owner、scope 与预算检查中复用,不读报告文件。 +`scripts/generate_semantic_inventory.py` 可按需导出同一份结构地图, +报告不入库;它每行一条地列出 Python 枚举、闭集、`Literal` 别名、 TypeScript `as const` 数组,以及拆为跨运行时孪生、同运行时分叉、冲突值、多值 孪生与多值分叉四类的重复定义。每个多值冲突都带上全部定义模块及其值集,因此 可评审的是分叉本身而不只是计数。消费者计数由 `--report` 打印,合并候选组通过 `merge_candidate_groups` 获取, -两者均不提交,因此普通的消费者改动不会碰这个文件;合并候选是建议性的,因为值集 +所有清单输出均不提交;合并候选是建议性的,因为值集 相同并不能证明是同一个概念。单模块的字符串常量只计数,不列出。 值是只增的。删除一个值、字段、owner 或关系属于 schema 缩减,遵循 `AGENTS.md` @@ -424,8 +540,8 @@ TypeScript `as const` 数组,以及拆为跨运行时孪生、同运行时分 新词表通过一个 PR 加入:新增注册表条目、提高覆盖下限,若存在 TypeScript owner 则指名其 `as const` 数组。当一个词表被多个模块分发或跨越 Python/TypeScript -边界时,即有资格进入策展层;其余由清单映射而不策展。新增任何载体都要在同一 -PR 中重新生成清单。 +边界时,即有资格进入策展层;其余由清单映射而不策展。新增载体会在下次全树扫描时自动发现, +真正的共享契约变化仍需评审。 ## 6. 备选方案与设计选择 @@ -438,13 +554,13 @@ PR 中重新生成清单。 | CI 里不带注册表的 grep 式 lint | 把允许集合编码进 linter,变成没有评审痕迹的第二份注册表。 | | 扩展 `maintainability_ratchet.py` 而不新建注册表 | 它的对象是模块指标与依赖方向,按模块设上限;词表形状需要值、owner 与关系。两者共享棘轮思想而非数据模型。例外生命周期是否合并见 Q7。 | | 把扫描正则放进注册表 | 数据里的正则可以在扩宽词表的同一次修改中被收窄;M0 评审表明第一版模式漏掉了全部 TypeScript `===` 分发点。形式固定在 smoke 里,后缀集合设下限。 | -| 在清单中提交消费者计数 | 每次消费者改动都会搅动文件,让新鲜度检查变成噪音。计数通过 `--report` 保持为参考信息。 | +| 提交计算清单或消费者计数 | 结构变化会产生没有新增语义权威的合并冲突。全树计算并按需导出报告;消费者计数保持为参考信息。 | ## 7. 安全、隐私与兼容 - M0 没有任何运行时路径导入注册表;检查存在与否,产品行为不变。 - 扫描器使用 `git ls-files --cached -z` 枚举索引中的源文件路径,再读取工作树内容。 - 未跟踪与忽略文件不进入清单;新增源文件需先暂存路径,再重新生成清单。已跟踪 + 未跟踪与忽略文件不进入清单;新增源文件需先暂存路径,再运行全树扫描。已跟踪 的符号链接与无法解析的 Python 源码使检查失败;运行时需要带 Git 元数据的检出。 - 字面量及 TypeScript 载体扫描同时识别单引号与双引号。它们仍是结构性文本 扫描,不是完整解析器,也不做数据流分析。 @@ -470,14 +586,14 @@ PR 中重新生成清单。 | 声明 | 测试或证据 | 要求结果 | 边界 / 排除 | | --- | --- | --- | --- | | 基线上注册表与清单和代码一致 | `uv run --extra test loopx canary smoke-suite --script semantic-vocabulary-drift-smoke.py` | `ok` 并输出覆盖、棘轮、预算与孪生报告 | 只证明已注册词表与已映射载体的一致性 | -| 清单新鲜 | `uv run python scripts/generate_semantic_inventory.py --check` | 退出码 0 | 仅结构性映射 | +| 清单按需计算 | `uv run python scripts/generate_semantic_inventory.py` | stdout 输出合法 JSON,不写仓库 | 完整已跟踪源码树,不仅是 PR diff | | 扫描器分类规则 | `uv run --extra test python -m pytest tests/architecture/test_semantic_inventory.py` | 通过 | 夹具仓库;规则来自本 RFC 而非输出 | | Python 侧扩宽 `effective_action` 时失败关闭 | 通过 `==`、成员测试或条件表达式加一个未注册字面量 | 失败文本命名该值与文件 | 突变练习;非提交测试 | | TypeScript 侧扩宽 `effective_action` 时失败关闭 | 通过 `===` 或三元表达式加一个未注册字面量 | 同上 | 同上 | -| 分叉常量时失败关闭 | 在非 owner 模块重定义 `TURN_ENVELOPE_SCHEMA_VERSION` 或 `HANDOFF_MODES`,重新生成清单 | 失败列出多出的定义模块或分叉预算 | 同上 | +| 分叉常量时失败关闭 | 在非 owner 模块重定义 `TURN_ENVELOPE_SCHEMA_VERSION` 或 `HANDOFF_MODES`,运行 smoke | 失败列出多出的定义模块或分叉预算 | 同上 | | Python 与 TypeScript owner 不能分叉 | 从已注册 `as const` 数组删一项,或扩宽已注册枚举 | 失败命名缺失或未注册的值 | 同上 | | 注册表不能仅靠改数据被削弱 | 声明裸模块 owner;删掉一个 owner;把后缀收窄为 `.py`;重命名一个被关系引用的词表;加一个未知键 | 每项都失败并点名规则 | 同上 | -| 新载体可见 | 新增一个枚举而不重新生成 | 失败文本说清单过期 | 同上 | +| 新载体可见 | 新增已跟踪枚举,不导出报告 | 当前扫描能看到它,不因报告新鲜度失败 | 变更与未变更文件之间的新分叉仍受预算约束 | | 冲突拼法不能增长 | 为已冲突名字加第三种值,重新生成 | 失败命名定义数预算 | 同上 | | 多值冲突不能增长 | 让一个闭集名在两个模块中以不同值集定义,或以相同值集定义,并重新生成 | `multi_value_forks` 或 `multi_value_twins` 失败并命名新名字 | 突变练习;非提交测试 | | 注册表不能放松自己的棘轮 | 在同一 diff 中调低任一 `coverage_floor` 计数、调高任一 `inventory_ratchets` 预算或退休预算,同时删掉它所统计的覆盖 | `COVERAGE_ANCHOR`、`BUDGET_ANCHOR` 或 `RETIREMENT_ANCHOR` 失败并命名被锚定的值 | 突变练习;挪动锚点是一次评审者可见的代码修改 | @@ -488,11 +604,12 @@ PR 中重新生成清单。 | 两处 owner 修正不改变行为 | `uv run --extra test python -m pytest tests/test_loopx_turn_transaction.py tests/test_loop_turn_loop_controller.py tests/test_turn_loop_disposition.py tests/test_loopx_turn_managed_step.py tests/control_plane -k authority` 与 `uv run --extra test loopx canary premerge --from-git-diff` | 通过 | 在干净树上可复现的 `main` 既有环境失败除外 | | 文档治理接受这对 RFC | `python3 examples/docs-governance-smoke.py` | 通过 | 检查镜像、链接、索引 | | 退休预算按子串而非标识符计数 | 分别以 `in file.text` 与 `\bgoal_boundary\b` 统计 `goal_boundary` | 基线上 35 对 30 个 Python 模块 | 已知边界;M3 的零读者门需要标识符计数,见第 12 节 | -| 模块局部约定过滤器是一次代码修改 | 扩宽 `inventory.py` 的 `MODULE_LOCAL_CONVENTION` 并重新生成 | `*_semantic` 预算下降而别处无代码改动 | 已知边界;正则在代码里,扩宽是可评审的 diff,未过滤总数仍在预算内 || 无人生产的注册值失败(M0.5) | 在基线上运行生产形式扫描 | 失败并点名 `effective_action` 与 `skip`;删除 `skip` 或列入 `compatibility_only` 后通过 | 第一个预期的 I12 失败;只被比较的值不算已携带 | +| 模块局部约定过滤器是一次代码修改 | 扩宽 `inventory.py` 的 `MODULE_LOCAL_CONVENTION` 并重新生成 | `*_semantic` 预算下降而别处无代码改动 | 已知边界;正则在代码里,扩宽是可评审的 diff,未过滤总数仍在预算内 | +| 无人生产的注册值失败(M0.5) | 在基线上运行生产形式扫描 | 失败并点名 `effective_action` 与 `skip`;删除 `skip` 或列入 `compatibility_only` 后通过 | 第一个预期的 I12 失败;只被比较的值不算已携带 | | 生产未注册值失败(M0.5) | 在某个已列生产位点写 `effective_action: "brand_new"` | 即使无消费者比较它也失败,并点名位点与值 | I13;生产比比较更严 | -| 有界上下文名字只能靠声明离开分叉预算(M0.5) | 为 `SOURCE_SURFACES` 声明四个上下文;另行只改名其中一处定义而不声明 | 声明把 `multi_value_forks` 降到 3;单独改名不降 | I14;诚实的修法是评审者看得见的注册表修改,改名是不碰注册表的代码改动 | +| 有界上下文名字只能靠声明离开语义分叉预算(M0.5a) | 为 `SOURCE_SURFACES` 声明四个上下文;另行只改名其中一处定义而不声明 | 原始 `multi_value_forks` 保持 4,`multi_value_forks_semantic` 为 3;单独改名既不改变语义计数,也不构成声明 | I14;诚实的修法是评审者看得见的注册表修改,改名不是修复 | -| 上游合并会让已提交清单过期 | 对 `upstream/main` 最近二十个合并提交,在第一父提交与合并结果之间重放扫描器 | 20 次合并中 8 次至少改变一个载体 | 提交快照的实测成本;处理规则见第 10 节与第 12 节 Q9 | +| 历史上的已提交清单会因上游合并而过期 | 对 `upstream/main` 最近二十个合并提交,在第一父提交与合并结果之间重放扫描器 | 20 次合并中 8 次至少改变一个载体 | Q9 的历史动机;当前检查直接计算合并后的全树,不再依赖提交快照 | | 形式模型不能静默丢失证明义务 | 从 `formal_model` 删除不变量、角色、候选决策、关系或证明边界分类 | 漂移 smoke 针对形式模型结构失败 | 该模型是有限契约和证明账本,本身不等于这些性质已经被证明 | 已知边界,写明是为了不让这个检查被过度信任: @@ -532,15 +649,19 @@ heartbeat/quota 覆盖。quick 与 deep 档位的上限不变。 工作流被刻意设为非 PR 必需检查。舰队能发现的 smoke 不是提交时检查,除非某个 必需的 PR 作业收集它。 -**合并序风险。** `inventory_v0.json` 是整个 `loopx/` 树的已提交快照,树与快照 -不一致时 smoke 失败。两个各自新增载体、各自正确再生成清单的 PR,对着它们 -各自基于的 `main` 都是绿的;后合并的那个会让 `main` 的快照缺少先合并者的 -条目,`main` 上的扫描在有人再生成之前是红的。对 `upstream/main` 最近二十次 -合并的重放显示有八次至少改变一个载体,所以这是每周会发生的事,不是边角。 -本分支第一次同步上游就复现了它:合入的十二个提交新增一个枚举与三个闭集, -检查失败直到重新生成。处理规则是第 12 节 Q9;在其决定之前的规则是:在 -`main` 变红之后合并 PR 的人负责跟一个只改 `inventory_v0.json` 的再生成提交, -smoke 的失败文本会点名那条命令。 +**按需清单(Q9)。** 原来的已提交快照为本来合法的 PR 增加了额外同步义务, +现予以取消。设 `f(T)` 为完整已跟踪源码树的清单,`G(f(T), R)` 为既有注册表、 +owner、scope 与预算谓词,检查仍执行 `G(f(T), R)`,只去掉附加条件 +`I_committed = f(T)`。所有相关守卫复用本次扫描结果,缺失或过期的本地报告 +不能掩盖新分叉。这不证明独立合法的分支合并后不会产生语义冲突;仍须验证 +合并后的源码树。不得用只扫描 PR diff 代替全树扫描。 + +查看清单用 `uv run python scripts/generate_semantic_inventory.py`,默认向 +stdout 输出 JSON;追加 `--output .local/semantic-inventory.json` 可导出报告。 +`--output --check` 只核对指定报告,不修改它;单独 `--check` 会给出 +迁移提示。报告可以作为 CI artifact,但不入库,也不是运行守卫的前置条件。 +绑定与术语表继续提交并检查新鲜度;本决策只移除仓库结构清单的提交义务, +不增加 CI 作业。 **解释器与源码。** 在目标 worktree 根目录通过 `uv run` 执行上面的命令。 Python 兼容范围来自 `pyproject.toml`(`>=3.11`),导入的 LoopX 必须来自当前源码。 @@ -549,12 +670,24 @@ Canary 将显示为 `python3` 的命令转换为启动 LoopX 的 `sys.executable 边界见[本地验证环境](../../development/testing-and-quality.md#local-validation-environment--本地验证环境)。 下方历史证据保留实际执行过的命令。 +已有完整环境时,也可以用 `bash scripts/loopx-python.sh --exec ` +自动选择已安装的兼容解释器,包括 `.venv/bin/python`,或通过 `LOOPX_PYTHON` +指定。该选择器不会安装 Python 和依赖。舰队与 premerge 的子命令仍可使用 +`python3`,由选定的项目或 CI 环境提供 `PATH`。 + +TypeScript effective-action 绑定与[术语表](../../reference/glossary.md)通过 +`uv run python scripts/generate_semantic_bindings.py` 生成。修改 Python owner +或注册表后运行该命令;载体变化会自动扫描,清单报告只按需导出。现有漂移 smoke 与 PR pytest +检查生成物新鲜度,不新增 required CI job。运行 TypeScript 生产者扫描之前, +先用 `npm ci --ignore-scripts` 安装锁定的 Node 依赖。 + ## 11. 规范性交付计划 | 里程碑 | 交付行为 | 进入门 | 退出证据 | 回滚 | | --- | --- | --- | --- | --- | -| M0 | 含 26 个词表与 9 条关系的注册表、带 `--check` 的生成清单、带固定分发形式与覆盖下限的漂移 smoke、删除两处 owner 分叉、RFC 索引条目 | 本 RFC 开启 | 第 9 节各行全绿;20 类突变失败关闭 | 删除 smoke、`loopx/semantics/`、生成器及其测试 | -| M0.5 | 含 `global` 与 `bounded_context` 及每上下文 owner 的 `scope`;`kernel` 词表上的 `producers` 与 `compatibility_only`;带两条角色检查(I12、I13)的生产形式扫描;退休预算改按标识符计数并在一个 diff 里调低全部六个锚点(Q11);Q9 的合并序规则写入第 10 节 | M0 合入;Q9 已决或其临时规则被接受 | smoke 在 I11 到 I14 强制下全绿;`skip` 已处理;`multi_value_forks` 靠声明降到 3;第 9 节角色行全绿;为 Q2 回答 `turn_route` 是否持久化 | 删除三个字段与角色检查;预算回到 M0 锚点 | +| M0 | 含 26 个词表与 9 条关系的注册表、可选导出的计算清单、带固定分发形式与覆盖下限的漂移 smoke、删除两处 owner 分叉、RFC 索引条目 | 本 RFC 开启 | 第 9 节各行全绿;20 类突变失败关闭 | 删除 smoke、`loopx/semantics/`、生成器及其测试 | +| M0.5a | `scope_declarations` 的 `bounded_context` 与每上下文 owner;把语义分叉计数与原始清单计数分开 | M0 合入 | smoke 校验每个声明的上下文 owner;原始 `multi_value_forks` 仍为 4,`multi_value_forks_semantic` 为 3;未声明分叉仍受预算约束 | 删除作用域声明和语义分叉预算 | +| M0.5b | `kernel` 词表的 `producers` 与 `compatibility_only`;带两条角色检查(I12、I13)的生产形式扫描;退休预算改按标识符计数并在一个 diff 里调整六个锚点(Q11);Q9 的合并序规则写入第 10 节 | M0.5a 完成;Q9 已决或其临时规则被接受 | smoke 在 I11 到 I14 强制下全绿;`skip` 已处理;第 9 节生产者行全绿;为 Q2 回答 `turn_route` 是否持久化 | 删除生产者字段和角色检查;预算回到 M0.5b 前的锚点 | | M1 | 单一 owner 模块中的 `EffectiveAction` 类型化枚举;replay observation 与 frontier 槽位拆出(Q6);生产者与消费者 import 它;注册表 `literal_scan` 收紧到枚举 | M0.5 合入;owner 模块已定(Q3);槽位拆分已决(Q6) | smoke 绿;owner 之外零裸 `effective_action` 字面量;status/should-run 的 parity fixture 不变 | 回退为字面量;注册表保留集合 | | M2 | route 到 disposition 的投影、`decide_loop_disposition` 决策表与跨运行时集合通过共享契约发布,生成 Python 与 TypeScript 绑定,效仿协调契约生成器 | M1 合入;Q2 与 Q7 已决 | 生成器 `--check` 与 smoke 绿;`settlement.ts` 与 `transaction.py` 读取生成集合 | 从上一版契约重新生成 | | M3 | 逐字段退休旧 should-run 字段,每个 PR 一个字段,预算降到零并删除字段 | 经生产者/读者调研证明该字段外部读者为零 | 按 `AGENTS.md` 的 schema 缩减记录;附录 B 条目 | 从最后一个写方恢复字段 | @@ -566,7 +699,7 @@ Canary 将显示为 `python3` 的命令转换为启动 LoopX 的 `sys.executable | 表面 | 基线(`1dc6ad8d8`) | 本 RFC 关闭时的目标 | 由谁达成 | | --- | --- | --- | --- | -| `effective_action` 取值 | 33 个字面量,无 owner 符号 | 一个枚举 owner;`skip`、`observe_replay`、`block_replay` 与两个 `quota_action_selection_*` 码从判定槽位移出;约 28 值 | M1 | +| `effective_action` 取值 | 33 个字面量,无 owner 符号 | 一个枚举 owner;`skip`、`observe_replay`、`block_replay` 与两个 `quota_action_selection_*` 码从判定槽位移出;计入五个此前漏记的生产值并移除合成 operator_gate 后,共 32 个决策值 | M1 | | 同一 envelope 里的 `effective_action` 槽位 | 一个字段名下 3 套词表 | 1,或在 Q6 保留字段时为一个已注册并集 | M1(Q6) | | Turn 词表 | 3 套、28 值、21 个不同值、7 个冗余拼法 | 保留 3 套;投影与决策表生成并校验;拼法不变,除非 Q10 决定合并 | M2(Q2、Q10 *未决*) | | 同运行时分叉(语义) | 18 个名字 | 0 | 基线窄 PR | @@ -638,26 +771,30 @@ PR review 保留这些层级。普通改动记录检查范围和理由,无共 投影覆盖全部输入但非单射(`blocked` 与 `wait` 都映到 `wait`),而 `stop`、 `terminal`、`contract_error` 只在一侧存在。`same_concept` 关系记录了四个共享 裁决。建议:两者都保留,M2 发布投影,待 managed-step 消费者成熟后再议。 - M2 前需定。保留两者的理由是合并会触及已持久化的 Turn 记录;这个前提尚未 - 核实。决定之前应先用 M0.5 的生产形式扫描(I12,第 5 节)确认 `turn_route` - 是否曾写入 journal 或 - receipt,还是只在进程内流转;若是后者,合并的代价远低于本 RFC 的假设, - 适用 Q10。 -3. **`EffectiveAction` 的 owner 模块。** 注册表今天不声明 owner,因为不存在任何 - 符号;字面量扫描是唯一检查。选项:`quota/should_run_packet.py`(最大生产者)、 - 新建 `quota/effective_action.py`,或按迁移 RFC 以 TypeScript `turn_envelope.ts` - 为 owner 并生成 Python 绑定。建议:若 M2 先落地则以 TypeScript 为 owner 并 - 生成 Python 绑定;否则新建 `quota/effective_action.py`。M1 前需定。 -4. **伴随术语表。** 是否新增 `docs/reference/glossary.md`,从注册表的 `meaning` - 字段与清单生成。Owner:文档维护者。建议:是,在 M1 生成以免漂移。 + 持久化前提已有实现证据:`run_loopx_turn_once` 经 TypeScript journal writer + 写入完整的 `plan: dict(plan)`,其中包含 `plan.route.kind`; + `load_loopx_turn_plan_from_journal` 会恢复这个 route。执行器的回放回归用例 + 检查实际落盘的 journal 及恢复读者。因此保留三套词表,在 M2 发布非单射投影; + 后续若改名,必须迁移持久化 plan,不能只做进程内枚举重构。此证据不等于全部 + 外部读者或其他持久化字段的兼容性证明。 +3. **`EffectiveAction` 的 owner 模块。** 实现选择 `quota/effective_action.py`, + 对应生成阶段之前的选项。运行时调用者通过 `.value` 保留现有字符串。 + TypeScript 消费者导入从该 Python 枚举生成的 + `quota/effective_action.generated.ts`,测试核对成员名与值的一致性。M2 可以 + 从共享契约生成两种绑定,并保留现有 import 路径;不得在运行时模块另写一份 + 独立维护的值表。 +4. **伴随术语表。** `docs/reference/glossary.md` 从注册表的语义、owner、值及 + 兼容性元数据生成,只覆盖已注册词表;清单仍是更广的结构地图。现有 smoke + 拒绝过期生成物。应修改 owner/注册表并重新生成,避免维护第二份文字权威。 + Owner:文档维护者。 5. **词族命名规则。** `gate`、`scope`、`packet`、`handoff`、`settlement` 词族中的 新标识符是否必须在评审中引用术语表条目。这是评审规则而非 smoke;建议在 术语表存在后纳入 first-review roster。 -6. **拆分 `effective_action` 的三个槽位。** decision 槽位、`agent_scope_frontier` - 槽位与 replay observation 槽位在同一个 Turn Envelope 里共用一个字段名、承载 - 三套词表;`skip` 被比较却从未被生产。选项:重命名 observation 与 frontier - 槽位,或保留一个字段并注册其并集。Owner:Turn Envelope owner。建议:在 M1 - 重命名,让 Q3 的枚举只有一种含义。M1 前需定。 +6. **动作槽位决策(Q6)。** 根 should-run/Turn Envelope 字段保留为代码锚点 + 固定、互不相交的决策/前沿并集。嵌套 frontier v1 使用既有 `action`,journal + replay 使用既有 `observation.decision`,不新增另一份冗余字段。读取历史 v0 + capsule 时保留已签名的旧字段,新写入使用版本化的缩减形状。见上文 M1 + 兼容表与测试;这不授权其他旧字段退休或 Turn 结果枚举合并。 7. **与 `maintainability_ratchet.py` 的关系。** 清单棘轮是否采用它的评审化例外 生命周期(`retirement_plan`、过期例外检测),还是保持为纯预算。建议:在 M2 生成落地时采用,让有书面理由的分叉可以被例外而非被预算。Owner:canary @@ -665,22 +802,18 @@ PR review 保留这些层级。普通改动记录检查范围和理由,无共 8. **从清单到注册表的晋升规则。** 外部消费者模块不少于三个或存在跨运行时孪生 的已映射载体是否必须策展。建议:现在作为评审规则采用,待清单积累一个季度 历史后再由 smoke 强制。Owner:内核维护者。 -9. **跨合并的清单新鲜度。** 两个新增载体的 PR 先后合并时,已提交快照会过期 - (第 10 节;上游最近二十次合并中八次)。选项:(a) 分支保护要求 PR 与 - `main` 同步,根除风险但拖慢所有 PR;(b) 合并者负责一个只再生成的后续提交, - 快照留在 git 历史里,接受 `main` 红几分钟;(c) 清单不入库,CI 只对 PR diff - 生成,失去对载体的 `git blame`。建议:现在用 (b),若 `main` 变红超过每周 - 一次则改 (a)。Owner:仓库维护者。这是运维决策不是代码改动;应放在跟踪 - issue 的决策清单里,而不是任务清单里。 +9. **跨合并的清单新鲜度(Q9)。** 采用全树按需计算与可选的不入库报告。 + 删除已提交清单及其逐字新鲜度义务,保留语义谓词、扫描范围、覆盖下限与预算。 + 这取代合并后另补再生成提交的建议,也不采用旧选项中只扫描 PR diff 的部分。 + 第 10 节规定命令和证明边界。 10. **Turn 词表的终态。** 第 11 节的目标表默认保留三套与七个冗余拼法,因为 - Q2 建议保留两者。若 Q2 的 M0.5 生产形式扫描表明 `turn_route` 未被持久化,维护者 - 应在 (a) 三套加生成投影(现行计划)与 (b) 两阶段合并(先双写、后退休)到 - 每个概念一种拼法之间选择。没有这个决定,RFC 对其标题问题只有预算、没有 - 完成定义。Owner:Turn driver owner。M2 关闭前需定。 -11. **退休预算按标识符计数。** 六个旧字段预算用 `field in file.text` 统计; - `goal_boundary` 会匹配 `goal_boundary_repair`。M3 的零外部读者门需要词边界 - 计数,这会在一个 diff 里调低全部六个锚点。建议:在第一个 M3 PR 之前做。 - Owner:内核维护者。 + Q2 建议保留两者。Q2 的实际写入及读回证据证明 `turn_route` 已持久化,因此 + 实现保留三套不同值集并生成投影,不合并拼法。未来合并提案须提供双读或带版本 + 的迁移及读者证据。Owner:Turn driver owner。 +11. **退休预算使用独立字段 token。** 六个旧字段预算现在使用 + `count_identifier_modules()`,因此 `goal_boundary_repair` 不会被算作 + `goal_boundary`。这是保守的词法指标,不等于证明不存在语义读者;计算式访问 + 仍然是证据缺口。Owner:内核维护者。 ## 附录 A:执行账本(非规范) @@ -812,7 +945,7 @@ PR review 保留这些层级。普通改动记录检查范围和理由,无共 | 日期 | 决策 | Owner / 批准 | 备选 | 变更的规范章节 | | --- | --- | --- | --- | --- | -| — | 尚无记录 | — | — | — | +| 2026-09-16 | Q9:全树按需计算;移除已提交结构清单 | 根据[维护者反馈](https://github.com/huangruiteng/loopx/pull/4360#issuecomment-5692062394)实现,PR 评审待完成 | 取代合并后补再生成;拒绝只扫描 diff | 1、I6、3、5、9、10、12 | ## 附录 C:证据登记 diff --git a/docs/development/testing-and-quality.md b/docs/development/testing-and-quality.md index c1fb7ab83e..1766a094c2 100644 --- a/docs/development/testing-and-quality.md +++ b/docs/development/testing-and-quality.md @@ -354,12 +354,17 @@ uv run --extra test python -m pytest -q uv run --extra test loopx canary premerge --from-git-diff # For a fork whose PR base is upstream/main, use this instead: uv run --extra test loopx canary premerge --from-git-diff --git-diff-base upstream/main -# Run one semantic smoke or check its generated inventory: +# Validate semantics; optionally inspect the full-tree inventory without writing it: uv run --extra test loopx canary smoke-suite --script semantic-vocabulary-drift-smoke.py -uv run python scripts/generate_semantic_inventory.py --check +uv run python scripts/generate_semantic_inventory.py git diff --check ``` +The semantic inventory is computed from the full tracked tree, not committed. +Use `--output .local/semantic-inventory.json` only when an exported report is useful; +`--output --check` checks that explicit report without repairing it. +词表、owner 与预算继续入库并受检查;结构清单按需计算,无须为普通 PR 补生成文件。 + Confirm the interpreter and imported checkout when diagnosing a mismatch: ```bash diff --git a/docs/reference/effect-interpreter-packet.md b/docs/reference/effect-interpreter-packet.md index fd25eca131..4b409d0f2a 100644 --- a/docs/reference/effect-interpreter-packet.md +++ b/docs/reference/effect-interpreter-packet.md @@ -26,6 +26,28 @@ decision or turn-settlement logic; they give refactor and test code one stable abstraction for reading the effect program shape across packet families. +The action slot depends on the packet family: + +| Observation | Verdict | `effective_action` | +| --- | --- | --- | +| Quota should-run | Quota decision | Existing decision/frontier action string | +| Turn result | `decision` carries `result_kind` | Always JSON `null` / Python `None` | +| Journal replay | `replay_legal` or `replay_blocked` | Omitted from the internal replay observation | + +Turn-result readers must use `decision` for the result verdict. The result lens +ignores a host-supplied action; it cannot create a quota decision. The TS result +type fixes its action to null, while the generic quota type retains its default +string action. This changes the transient result projection, not persisted +host results, receipts, or journal plans. See the +[semantic vocabulary RFC](../architecture/rfcs/semantic-vocabulary-convergence-v0.md#m1-action-domains-and-compatibility) +for the versioned frontier migration and its compatibility boundary. + +动作槽位按 packet family 区分:quota 保留决策/前沿动作字符串;Turn result 的 +判决从 `decision` 读取,`effective_action` 固定为 `null`(Python 为 `None`); +journal replay 使用自己的判决,不再输出动作字段。host 提供的 action 不会被 +解释为 quota 决策。这只改变瞬时结果投射,不重写落盘的 host result、receipt +或 journal plan。 + ## Turn Journal Lens `interpret_turn_journal` reads an existing fenced Turn journal and returns an diff --git a/docs/reference/glossary.md b/docs/reference/glossary.md new file mode 100644 index 0000000000..37c4f5f961 --- /dev/null +++ b/docs/reference/glossary.md @@ -0,0 +1,246 @@ +# Semantic Vocabulary Glossary / 语义词表 + + + +This is a generated view of the curated registry, not another authority. +这是注册表的生成视图;修改定义应编辑 owner 和注册表,然后重新生成。 + +Equal spellings or value sets do not prove equal meaning. Producers write +values; consumers interpret or pass them through; only owners define sets. +同名或相同值集不等于同一语义。生产者写值,消费者解释或透传,owner 定义集合。 + +See the [RFC](../architecture/rfcs/semantic-vocabulary-convergence-v0.md) +and its [中文版本](../architecture/rfcs/semantic-vocabulary-convergence-v0.zh-CN.md) +for scope, compatibility, proof boundaries and migration gates. + +## agent_scope_frontier_action + +Frontier verdict for an agent-scoped lane. New v1 payloads use agent_scope_frontier.action; the root should-run effective_action projects the same value through its registered disjoint union. Legacy v0 signed payloads remain readable without rewriting. + +- Tier / 层级: `kernel`; status / 状态: `canonical`. +- python: [`AgentScopeFrontierAction`](../../loopx/control_plane/agents/agent_scope_frontier.py). +- typescript: [`AGENT_SCOPE_FRONTIER_ACTIONS`](../../loopx/control_plane/agents/agent_scope_frontier.generated.ts). +- Values / 值: `agent_scope_exhausted`, `agent_scope_wait`, `reassignment_required`, `successor_replan_required`. + +## delivery_continuity_preemption + +Reason a delivery continuity is preempted. + +- Tier / 层级: `cross_runtime`; status / 状态: `canonical`. +- python: [`DELIVERY_CONTINUITY_PREEMPTIONS`](../../loopx/control_plane/turn_driver/delivery_continuity.py). +- typescript: [`DELIVERY_CONTINUITY_PREEMPTIONS`](../../loopx/control_plane/turn_driver/delivery_continuity.ts). +- Values / 值: `heartbeat_receipt`, `blocking_work_lane`, `autonomous_replan`, `control_repair`, `delivery_not_allowed`. + +## delivery_outcome + +Outcome class of a delivered work item. + +- Tier / 层级: `cross_runtime`; status / 状态: `canonical`. +- python: [`DeliveryOutcome`](../../loopx/control_plane/work_items/delivery_outcome.py). +- typescript: [`DELIVERY_OUTCOMES`](../../loopx/control_plane/work_items/delivery_outcome.ts). +- Values / 值: `surface_only`, `outcome_gap`, `outcome_progress`, `primary_goal_outcome`. + +## delivery_workspace_identity_kind + +How a delivery workspace is identified. + +- Tier / 层级: `cross_runtime`; status / 状态: `canonical`. +- python: [`DELIVERY_WORKSPACE_IDENTITY_KINDS`](../../loopx/control_plane/agents/delivery_workspace.py). +- typescript: [`DELIVERY_WORKSPACE_IDENTITY_KINDS`](../../loopx/control_plane/agents/delivery_workspace.ts). +- Values / 值: `git_repository`, `local_goal`. + +## delivery_workspace_kind + +Workspace kind a delivery runs in. + +- Tier / 层级: `cross_runtime`; status / 状态: `canonical`. +- python: [`DELIVERY_WORKSPACE_KINDS`](../../loopx/control_plane/agents/delivery_workspace.py). +- typescript: [`DELIVERY_WORKSPACE_KINDS`](../../loopx/control_plane/agents/delivery_workspace.ts). +- Values / 值: `canonical_checkout`, `independent_git_worktree`, `local_goal_workspace`. + +## delivery_workspace_requirement + +Workspace requirement a settlement asserts before delivery. + +- Tier / 层级: `cross_runtime`; status / 状态: `canonical`. +- python: [`DELIVERY_WORKSPACE_REQUIREMENTS`](../../loopx/control_plane/quota/settlement_workspace_causality.py). +- typescript: [`DELIVERY_WORKSPACE_REQUIREMENTS`](../../loopx/control_plane/quota/settlement_workspace_causality.ts). +- Values / 值: `required`, `not_required`, `unknown`. + +## effective_action + +Compacted should-run verdict carried by status/should-run payloads and the Turn Envelope; the Python enum owns the finite value domain while the wire field remains a string for compatibility. + +- Tier / 层级: `kernel`; status / 状态: `merge_candidate`. +- python: [`EffectiveAction`](../../loopx/control_plane/quota/effective_action.py). +- typescript: [`EFFECTIVE_ACTIONS`](../../loopx/control_plane/quota/effective_action.generated.ts). +- Values / 值: `agent_monitor_only`, `agent_workspace_repair`, `automation_prompt_upgrade_required`, `autonomous_replan_required`, `blocked_health`, `blocked_wait`, `boundary_projection_repair`, `capability_bridge_repair`, `control_plane_health_repair`, `control_plane_projection_repair`, `control_plane_repair`, `coordinate_task_bundle`, `external_evidence_observe`, `governed_capability_intent`, `heartbeat_receipt_write_failed`, `heartbeat_settled_skip`, `lark_inbox_reply_due`, `monitor_due`, `monitor_quiet_skip`, `normal_run`, `operator_gate_notify`, `operator_inbox_material_review_due`, `outcome_floor_recovery`, `peer_coordination_blocked`, `quota_skip`, `runtime_user_gate_projection_repair`, `scoped_user_gate_fallback`, `state_projection_gap_repair`, `terminal_no_followup`, `throttled_skip`, `todo_decision_scope_projection_repair`, `unsettled_host_turn_recovery`. + +## goal_amendment_class + +Class of a proposed Goal amendment. + +- Tier / 层级: `cross_runtime`; status / 状态: `canonical`. +- python: [`GOAL_AMENDMENT_CLASSES`](../../loopx/control_plane/goals/goal_amendment_proposal.py). +- typescript: [`GOAL_AMENDMENT_CLASSES`](../../loopx/control_plane/goals/goal_amendment_proposal.ts). +- Values / 值: `lane_route`, `shared_work_graph`, `shared_acceptance`, `protected_authority`. + +## goal_amendment_proposal_admission + +Admission verdict for a Goal amendment proposal. + +- Tier / 层级: `cross_runtime`; status / 状态: `canonical`. +- python: [`GOAL_AMENDMENT_PROPOSAL_ADMISSIONS`](../../loopx/control_plane/goals/goal_amendment_proposal.py). +- typescript: [`GOAL_AMENDMENT_PROPOSAL_ADMISSIONS`](../../loopx/control_plane/goals/goal_amendment_proposal.ts). +- Values / 值: `admitted`, `needs_rebase`. + +## goal_amendment_proposal_admission_fact + +Fact recorded with a Goal amendment admission verdict. + +- Tier / 层级: `cross_runtime`; status / 状态: `canonical`. +- python: [`GOAL_AMENDMENT_PROPOSAL_ADMISSION_FACTS`](../../loopx/control_plane/goals/goal_amendment_proposal.py). +- typescript: [`GOAL_AMENDMENT_PROPOSAL_ADMISSION_FACTS`](../../loopx/control_plane/goals/goal_amendment_proposal.ts). +- Values / 值: `base_state_event_basis_sequence_behind_derived_head`, `base_source_basis_digest_mismatch`, `base_source_basis_unverifiable`, `base_revision_basis_superseded`. + +## handoff_mode + +Authority handoff mode between agents. + +- Tier / 层级: `cross_runtime`; status / 状态: `canonical`. +- python: [`HandoffMode`](../../loopx/control_plane/coordination/authority_core.py). +- typescript: [`HANDOFF_MODES`](../../loopx/control_plane/coordination/handoff_mode_policy.ts). +- Values / 值: `legacy`, `soft_claim`, `hard_lease`. + +## lease_action + +Authority-core lease mutation verb. + +- Tier / 层级: `kernel`; status / 状态: `legacy`. +- python: [`LeaseAction`](../../loopx/control_plane/coordination/authority_core.py). +- Values / 值: `acquire`, `renew`, `transfer`, `release`. +- Compatibility only / 兼容保留: `acquire`, `release`, `renew`, `transfer`. + +## loop_disposition + +Pure controller verdict for the outer loop after combining the last Turn receipt with the fresh route. + +- Tier / 层级: `kernel`; status / 状态: `canonical`. +- python: [`LoopDisposition`](../../loopx/control_plane/turn_driver/loop_controller.py). +- Values / 值: `run_now`, `capability_action_required`, `wait`, `stop`, `user_action_required`, `repair`, `replan`, `terminal`. + +## receipt_bound_monitor_phase + +Receipt-bound phase of a monitor poll settlement. + +- Tier / 层级: `cross_runtime`; status / 状态: `canonical`. +- python: [`ReceiptBoundMonitorPhase`](../../loopx/control_plane/effect_program.py). +- typescript: [`RECEIPT_BOUND_MONITOR_PHASES`](../../loopx/control_plane/quota/settlement_phase.ts). +- Values / 值: `poll_due`, `settlement_pending`, `settled`. + +## receipt_bound_replay_phase + +Receipt-bound phase of a Turn journal replay settlement. + +- Tier / 层级: `cross_runtime`; status / 状态: `canonical`. +- python: [`ReceiptBoundReplayPhase`](../../loopx/control_plane/effect_program.py). +- typescript: [`RECEIPT_BOUND_REPLAY_PHASES`](../../loopx/control_plane/quota/settlement_phase.ts). +- Values / 值: `open`, `settlement_pending`, `settled`. + +## scheduler_cadence_transition + +Legal scheduler cadence state transition. + +- Tier / 层级: `cross_runtime`; status / 状态: `canonical`. +- python: [`SchedulerCadenceTransition`](../../loopx/control_plane/scheduler/state_transition_rules.py). +- typescript: [`SCHEDULER_CADENCE_TRANSITIONS`](../../loopx/control_plane/scheduler/state_transition_rules.ts). +- Values / 值: `initial`, `identity_reset`, `retry_unacknowledged_failure`, `hold_active_initial`, `advance_after_interval`, `hold_until_interval`. + +## scheduler_host_transition + +Legal scheduler host state transition. + +- Tier / 层级: `cross_runtime`; status / 状态: `canonical`. +- python: [`SchedulerHostTransition`](../../loopx/control_plane/scheduler/state_transition_rules.py). +- typescript: [`SCHEDULER_HOST_TRANSITIONS`](../../loopx/control_plane/scheduler/state_transition_rules.ts). +- Values / 值: `apply_required`, `host_match_ack_required`, `recorded_failure_suppressed`, `settled`. + +## settlement_binding_kind + +How a settlement step binds to its receipt. + +- Tier / 层级: `cross_runtime`; status / 状态: `canonical`. +- python: [`SettlementBindingKind`](../../loopx/control_plane/effect_program.py). +- typescript: [`SETTLEMENT_BINDING_KINDS`](../../loopx/control_plane/effect_program.ts). +- Values / 值: `todo`, `autonomous_replan`, `unbound`. + +## settlement_failure_kind + +Typed failure class of a settlement step. + +- Tier / 层级: `cross_runtime`; status / 状态: `canonical`. +- python: [`SettlementFailureKind`](../../loopx/control_plane/effect_program.py). +- typescript: [`SETTLEMENT_FAILURE_KINDS`](../../loopx/control_plane/effect_program.ts). +- Values / 值: `invalid_identity`, `receipt_missing`, `identity_mismatch`, `writeback_missing`, `writeback_rejected`, `quota_spend_rejected`, `terminal_closeout_rejected`, `cancelled`, `permission_denied`, `budget_rejected`, `effect_outcome_unknown`. + +## settlement_step_kind + +Effect-program settlement step executed for one Turn. + +- Tier / 层级: `cross_runtime`; status / 状态: `canonical`. +- python: [`SettlementStepKind`](../../loopx/control_plane/effect_program.py). +- typescript: [`SETTLEMENT_STEP_KINDS`](../../loopx/control_plane/effect_program.ts). +- Values / 值: `validation`, `durable_writeback`, `quota_spend`, `terminal_closeout`. + +## todo_completion_continuation + +Continuation declared by a completing Todo. + +- Tier / 层级: `cross_runtime`; status / 状态: `canonical`. +- python: [`TodoCompletionContinuation`](../../loopx/control_plane/todos/completion_state.py). +- typescript: [`TODO_COMPLETION_CONTINUATIONS`](../../loopx/control_plane/todos/completion_state.ts). +- Values / 值: `active_goal`, `successor`, `no_followup`. + +## todo_completion_recovery + +Recovery path when a Todo completion cannot continue. + +- Tier / 层级: `cross_runtime`; status / 状态: `canonical`. +- python: [`TodoCompletionRecovery`](../../loopx/control_plane/todos/completion_state.py). +- typescript: [`TODO_COMPLETION_RECOVERIES`](../../loopx/control_plane/todos/completion_state.ts). +- Values / 值: `same_turn_terminal_closeout`, `lifecycle_reentry_terminal_closeout`. + +## todo_decision_scope_granularity + +Granularity of a Todo decision scope. + +- Tier / 层级: `cross_runtime`; status / 状态: `canonical`. +- python: [`TODO_DECISION_SCOPE_GRANULARITY_VALUES`](../../loopx/control_plane/todos/contract.py). +- typescript: [`TODO_DECISION_SCOPE_GRANULARITIES`](../../loopx/control_plane/todos/decision_metadata.ts). +- Values / 值: `action`, `lane`, `goal`, `project`, `global`. + +## todo_decision_scope_kind + +Scope kind of a Todo decision. + +- Tier / 层级: `cross_runtime`; status / 状态: `canonical`. +- python: [`TODO_DECISION_SCOPE_KIND_VALUES`](../../loopx/control_plane/todos/contract.py). +- typescript: [`TODO_DECISION_SCOPE_KINDS`](../../loopx/control_plane/todos/decision_metadata.ts). +- Values / 值: `private_read`, `write_scope`, `resource`, `production`, `public_claim`, `direction`, `other`. + +## turn_result_kind + +Typed public outcome of one executed Turn as settled by the Turn Journal. + +- Tier / 层级: `kernel`; status / 状态: `canonical`. +- python: [`LoopXTurnResultKind`](../../loopx/control_plane/turn_driver/transaction.py). +- typescript: [`TURN_RESULT_KINDS`](../../loopx/control_plane/turn_driver/settlement.ts). +- Values / 值: `validated_progress`, `validated_completion`, `repair_required`, `replan_required`, `user_action_required`, `wait`, `iteration_failed`, `host_failure`, `validation_failed`, `writeback_failed`, `quota_spend_failed`, `terminal_closeout_failed`. + +## turn_route + +Typed delivery route derived from a fresh should-run decision before a Host is engaged. + +- Tier / 层级: `kernel`; status / 状态: `canonical`. +- python: [`LoopXTurnRoute`](../../loopx/control_plane/turn_driver/driver.py). +- Values / 值: `ready_for_host`, `capability_action_required`, `repair_required`, `replan_required`, `user_action_required`, `wait`, `blocked`, `contract_error`. diff --git a/examples/control_plane/agent-scope-projection-characterization-smoke.py b/examples/control_plane/agent-scope-projection-characterization-smoke.py index b44cffafa8..0bec4bfa15 100644 --- a/examples/control_plane/agent-scope-projection-characterization-smoke.py +++ b/examples/control_plane/agent-scope-projection-characterization-smoke.py @@ -258,9 +258,9 @@ def assert_agent_scope_frontier_builder_contract() -> None: extra_fields={"cleared_without_successor_handoff_gates": [{"todo_id": "todo_gate"}]}, ) - assert payload["schema_version"] == "agent_scope_frontier_v0" + assert payload["schema_version"] == "agent_scope_frontier_v1" assert payload["action"] == "successor_replan_required" - assert payload["effective_action"] == payload["action"] + assert "effective_action" not in payload assert payload["blocks_delivery"] is True assert payload["requires_replan"] is True assert payload["quiet_noop_allowed"] is False @@ -302,7 +302,7 @@ def assert_agent_scope_frontier_and_hint_contract() -> None: ) assert frontier is not None, frontier - assert frontier["schema_version"] == "agent_scope_frontier_v0" + assert frontier["schema_version"] == "agent_scope_frontier_v1" assert frontier["action"] == "successor_replan_required" assert frontier["requires_replan"] is True assert frontier["quiet_noop_allowed"] is False diff --git a/examples/control_plane/work-lane-contract-smoke.py b/examples/control_plane/work-lane-contract-smoke.py index eb544e821b..f9e67324c1 100644 --- a/examples/control_plane/work-lane-contract-smoke.py +++ b/examples/control_plane/work-lane-contract-smoke.py @@ -11,24 +11,24 @@ sys.path.insert(0, str(SMOKE_DIR)) sys.path.insert(0, str(REPO_ROOT)) -from loopx.control_plane.scheduler.execution_context import ( +from loopx.control_plane.scheduler.execution_context import ( # noqa: E402 - standalone smoke bootstraps repo imports SchedulerRuntimeProfile, scheduler_execution_context_for_runtime_profile, ) -from loopx.control_plane.todos.contract import ( +from loopx.control_plane.todos.contract import ( # noqa: E402 - standalone smoke bootstraps repo imports TODO_TASK_CLASS_ADVANCEMENT, TODO_TASK_CLASS_MONITOR, ) -from loopx.quota import ( +from loopx.quota import ( # noqa: E402 - standalone smoke bootstraps repo imports build_quota_should_run as _build_quota_should_run, render_quota_should_run_markdown, ) -from loopx.status import ( +from loopx.status import ( # noqa: E402 - standalone smoke bootstraps repo imports compact_todo_group, compact_post_handoff_run, normalize_todo_task_class, ) -from work_lane_contract_fixtures import ( +from work_lane_contract_fixtures import ( # noqa: E402 - standalone smoke bootstraps repo imports FUTURE_DUE_AT, GOAL_ID, PAST_DUE_AT, @@ -1393,8 +1393,9 @@ def assert_peer_requires_reassignment_when_only_other_peer_has_claimed_work() -> assert guard["effective_action"] == "reassignment_required", guard assert "agent_lane_next_action" not in guard, guard frontier = guard["agent_scope_frontier"] - assert frontier["schema_version"] == "agent_scope_frontier_v0", frontier + assert frontier["schema_version"] == "agent_scope_frontier_v1", frontier assert frontier["action"] == "reassignment_required", frontier + assert "effective_action" not in frontier, frontier assert frontier["agent_id"] == "codex-side-bypass", frontier assert "primary_agent" not in frontier, frontier assert frontier["candidate_counts"]["current_agent_claimed_advancement_count"] == 0, frontier diff --git a/examples/semantic-vocabulary-drift-smoke.py b/examples/semantic-vocabulary-drift-smoke.py index 1c02c159f5..d4808b9f36 100755 --- a/examples/semantic-vocabulary-drift-smoke.py +++ b/examples/semantic-vocabulary-drift-smoke.py @@ -3,11 +3,11 @@ ``loopx/semantics/vocabulary_v0.json`` names each kernel and cross-runtime vocabulary, the exact ``module::Symbol`` allowed to define it, how vocabularies -relate, and the budgets the repository ratchets down. ``inventory_v0.json`` is -the generated map of every closed-set carrier under ``loopx/``. This smoke -checks code against both so a PR that widens a vocabulary, forks a constant, -adds an unmapped carrier, or weakens the registry itself must show that change -in the same diff. It reads tracked sources only and prints no private data. +relate, and the budgets the repository ratchets down. The inventory is computed +from the complete tracked tree on each run, never loaded from a report file. +Vocabulary changes, forks and registry weakening remain checked; ordinary +carrier edits require no generated snapshot commit. Only tracked sources are +read, and no private data is printed. """ from __future__ import annotations @@ -29,24 +29,40 @@ collect_string_constants, load_sources, python_facts, - render_inventory, string_constant_definitions, typescript_facts, ) +from loopx.semantics.production import ( # noqa: E402 + collect_production, validate_production, probe_turn_result_input_domain, quota_action_domain, collect_literal_uses, +) +from loopx.semantics.python_production import scan_python_production # noqa: E402 +from scripts.generate_semantic_bindings import build_artifacts # noqa: E402 + REGISTRY_PATH = REPO_ROOT / "loopx" / "semantics" / "vocabulary_v0.json" REGISTRY_SCHEMA_VERSION = "loopx_semantic_vocabulary_v0" VALUE_SHAPE = re.compile(r"^[a-z][a-z0-9_]*$") +SYMBOL_NAME = re.compile(r"^[A-Za-z_][A-Za-z0-9_]*$") OWNER_SHAPE = re.compile(r"^[A-Za-z0-9_./-]+\.(py|ts)::[A-Za-z_][A-Za-z0-9_]*$") -QUOTED = re.compile(r'''["']([^"']*)["']''') REGISTRY_KEYS = { - "schema_version", "rfc", "inventory", "policy", "coverage_floor", "vocabularies", "relations", + "schema_version", "rfc", "policy", "coverage_floor", "vocabularies", "relations", "projections", "schema_versions", "retirement_ledger", "dual_runtime_twins", "inventory_ratchets", - "formal_model", + "formal_model", "scope_declarations", } VOCABULARY_KEYS = {"meaning", "tier", "status", "owners", "values"} -VOCABULARY_OPTIONAL_KEYS = {"literal_scan", "variable_sourced_values", "value_notes", "deprecated_values"} +VOCABULARY_OPTIONAL_KEYS = { + "literal_scan", + "variable_sourced_values", + "value_notes", + "deprecated_values", + "producers", + "compatibility_only", + "return_producers", + "return_paths", + "call_producers", + "input_producer", +} TIERS = {"kernel", "cross_runtime", "cross_module"} STATUSES = {"canonical", "legacy", "merge_candidate"} FORMAL_MODEL_KEYS = { @@ -87,7 +103,7 @@ # silently; that is the gap the anchor exists to close. COVERAGE_ANCHOR = { "vocabularies": 26, - "owner_symbols": 46, + "owner_symbols": 49, "literal_scan_fields": 1, "projections": 1, "relations": 9, @@ -95,6 +111,30 @@ } COVERAGE_SUFFIX_ANCHOR = (".py", ".ts") LITERAL_SCAN_ROOTS = ["loopx"] +PRODUCER_VOCABULARY_ANCHOR = { + "effective_action", "turn_route", "loop_disposition", "agent_scope_frontier_action", "turn_result_kind", "lease_action", +} +RETURN_PRODUCER_ANCHOR = { + "turn_route": {"loopx/control_plane/turn_driver/driver.py::_typed_route", "loopx/control_plane/turn_driver/loop_controller.py::_envelope_route", "loopx/control_plane/turn_driver/driver.py::build_loopx_turn_plan"}, + "loop_disposition": {"loopx/control_plane/turn_driver/loop_controller.py::_route_to_disposition"}, + "effective_action": {"loopx/control_plane/quota/decision_summary.py::quota_effective_action", "loopx/control_plane/quota/decision_summary.py::_task_orchestration_effective_action"}, + "turn_result_kind": {"loopx/control_plane/turn_driver/executor.py::_task_validation_receipt"}, +} +# Reviewed output arguments/paths replace the old any-enum-use heuristic. These +# anchors retain that coverage if registry metadata is accidentally removed. +CALL_PRODUCER_ANCHOR = { + "loop_disposition": {"loopx/control_plane/turn_driver/loop_controller.py::_disposition": ["disposition"]}, + "agent_scope_frontier_action": {"loopx/control_plane/agents/agent_scope_frontier.py::build_agent_scope_frontier_payload": ["action"]}, + "turn_result_kind": { + "loopx/control_plane/turn_driver/executor.py::_host_failure": ["kind"], + "loopx/control_plane/turn_driver/executor.py::_task_validation_receipt": ["recovery_kind"], + }, +} +RETURN_PATH_ANCHOR = { + "effective_action": {"loopx/control_plane/quota/decision_summary.py::_task_orchestration_effective_action": [0]}, + "turn_route": {"loopx/control_plane/turn_driver/driver.py::build_loopx_turn_plan": ["route", "kind"]}, + "turn_result_kind": {"loopx/control_plane/turn_driver/executor.py::_task_validation_receipt": ["recovery_kind"]}, +} TWIN_ROOT_ANCHOR = "loopx/control_plane" TWIN_BUDGET_ANCHOR = 43 BUDGET_ANCHOR = { @@ -105,6 +145,7 @@ "schema_version_same_runtime_forks": 7, "multi_value_twins": 19, "multi_value_forks": 4, + "multi_value_forks_semantic": 3, "multi_value_fork_definitions": 10, "same_runtime_forks_semantic": 18, "conflicting_values_semantic": 2, @@ -112,12 +153,12 @@ # Budgets for the legacy should-run decision fields, anchored the same way so a # single diff cannot widen a retirement budget to keep a field alive. RETIREMENT_ANCHOR = { - "execution_obligation": (21, 1), - "heartbeat_recommendation": (18, 1), - "work_lane_contract": (32, 3), - "external_evidence_observation": (11, 1), - "goal_boundary": (35, 2), - "protocol_action_packet": (7, 2), + "execution_obligation": (20, 1), + "heartbeat_recommendation": (17, 1), + "work_lane_contract": (29, 3), + "external_evidence_observation": (8, 1), + "goal_boundary": (30, 2), + "protocol_action_packet": (5, 2), } RATCHET_KEYS = ( "same_runtime_forks", @@ -127,26 +168,12 @@ "schema_version_same_runtime_forks", "multi_value_twins", "multi_value_forks", + "multi_value_forks_semantic", "multi_value_fork_definitions", "same_runtime_forks_semantic", "conflicting_values_semantic", ) -# Dispatch forms the literal scan recognises. Fixed here, not in the registry, so -# the registry cannot narrow what the scan sees. ``{f}`` is the field name. -DISPATCH_FORMS = ( - # Python/TypeScript comparisons, including wrapped field reads. - r"""{f}\b[^\n]*?(?:===|!==|==|!=)\s*["']([^"']*)["']""", - # Assignment or object/dict key. - r"""{f}["\'\]\)]*\s*(?::|=|\bis)\s*["']([^"']*)["']""", - # TypeScript conditional expression. - r"""{f}\b[^"\n]*?\?\s*["']([^"']*)["']\s*:\s*["']([^"']*)["']""", - # Membership in an inline collection. - r"""{f}["\'\]\)]*[^"\n]*?\bin\s*[\(\[\{{]([^\)\]\}}]*)[\)\]\}}]""", - # Python conditional expression. - r"""{f}\b[^"\n]*?=\s*["']([^"']*)["']\s+if\b[^"\n]*?\belse\s+["']([^"']*)["']""", -) -MEMBERSHIP_FORM_INDEX = 3 class Drift(AssertionError): @@ -167,7 +194,6 @@ def load_registry() -> dict[str, Any]: require(registry["schema_version"] == REGISTRY_SCHEMA_VERSION, f"registry schema_version must be {REGISTRY_SCHEMA_VERSION}") check_formal_model(registry["formal_model"]) require((REPO_ROOT / registry["rfc"]).is_file(), f"registry must point at an existing RFC: {registry['rfc']}") - require((REPO_ROOT / registry["inventory"]).is_file(), f"registry must point at an existing inventory: {registry['inventory']}") for name, vocabulary in registry["vocabularies"].items(): require(VALUE_SHAPE.match(name) is not None, f"vocabulary name must be lower snake_case: {name}") keys = set(vocabulary) @@ -192,6 +218,33 @@ def load_registry() -> dict[str, Any]: extra = set(vocabulary.get(key, {})) - set(values) require(not extra, f"{name}: {key} names unregistered values {sorted(extra)}") require(set(vocabulary.get("deprecated_values", [])) <= set(values), f"{name}: deprecated_values must be a subset of values") + producers = vocabulary.get("producers") + if producers is not None: + require(isinstance(producers, list), f"{name}: producers must be a list") + require(bool(producers) or set(vocabulary.get('compatibility_only', {})) == set(values), f"{name}: empty producers require every value to be compatibility-only") + require(all(isinstance(site, str) and OWNER_SHAPE.match(site) for site in producers), f"{name}: producers must be module::Symbol sites") + if 'input_producer' in vocabulary: + require(name == 'turn_result_kind', f"{name}: no executable input producer verifier is implemented") + require(vocabulary['input_producer'] == 'loopx/control_plane/turn_driver/transaction.py::_result_kind', f"{name}: unrecognised input producer") + returns = vocabulary.get("return_producers", []) + require(isinstance(returns, list) and all(isinstance(site, str) and OWNER_SHAPE.match(site) for site in returns), f"{name}: return_producers must be module::Symbol sites") + require(set(returns) <= set(producers or []), f"{name}: return_producers must also be registered producers") + paths = vocabulary.get('return_paths', {}) + require(isinstance(paths, dict) and set(paths) <= set(returns), f"{name}: return_paths must name declared return producers") + require(all(isinstance(path, list) and path and all(type(key) in (str, int) for key in path) + for path in paths.values()), f"{name}: return_paths must select literal fields or tuple indexes") + calls = vocabulary.get('call_producers', {}) + require(isinstance(calls, dict), f"{name}: call_producers must be a builder-to-parameters object") + require(all(isinstance(site, str) and OWNER_SHAPE.match(site) and isinstance(names, list) and names + and all(isinstance(arg, str) and arg.isidentifier() for arg in names) + and len(names) == len(set(names)) for site, names in calls.items()), + f"{name}: call_producers must name qualified builders and distinct parameters") + compatibility = vocabulary.get("compatibility_only") + if compatibility is not None: + require(isinstance(compatibility, dict), f"{name}: compatibility_only must be an object") + for value, metadata in compatibility.items(): + require(isinstance(metadata, dict) and set(metadata) == {"reason", "retirement"}, f"{name}: compatibility_only.{value} needs reason and retirement") + require(all(isinstance(item, str) and item.strip() for item in metadata.values()), f"{name}: compatibility_only.{value} metadata must be non-empty text") scan = vocabulary.get("literal_scan") if scan is not None: require(set(scan) == {"field", "roots", "suffixes"}, f"{name}: literal_scan keys must be field, roots, suffixes") @@ -256,6 +309,23 @@ def check_formal_model(model: dict[str, Any]) -> None: def check_coverage_floor(registry: dict[str, Any]) -> str: + try: + quota_action_domain(registry) + except ValueError as error: + raise Drift(str(error)) from error + require( + registry['vocabularies']['turn_result_kind'].get('input_producer') == 'loopx/control_plane/turn_driver/transaction.py::_result_kind', + 'turn_result_kind: input producer coverage must retain the anchored decoder', + ) + for name in PRODUCER_VOCABULARY_ANCHOR: + require("producers" in registry["vocabularies"][name], f"{name}: producer coverage dropped below PRODUCER_VOCABULARY_ANCHOR") + for name, required in RETURN_PRODUCER_ANCHOR.items(): + actual_returns = set(registry['vocabularies'][name].get('return_producers', [])) + require(required <= actual_returns, f"{name}: return producer coverage dropped below RETURN_PRODUCER_ANCHOR") + for metadata, anchor in (("call_producers", CALL_PRODUCER_ANCHOR), ("return_paths", RETURN_PATH_ANCHOR)): + for name, vocabulary in registry['vocabularies'].items(): + require(vocabulary.get(metadata, {}) == anchor.get(name, {}), + f"{name}: {metadata} must retain anchored output evidence exactly (default empty)") for vocabulary in registry["vocabularies"].values(): if scan := vocabulary.get("literal_scan"): require(scan["roots"] == LITERAL_SCAN_ROOTS, "literal_scan roots must cover loopx") @@ -336,20 +406,9 @@ def check_owned_vocabularies(registry: dict[str, Any], inventory: dict[str, Any] def scan_literals(field: str, roots: list[str], suffixes: list[str], sources: list[SourceFile]) -> dict[str, set[str]]: - observed: dict[str, set[str]] = {} - forms = [re.compile(form.format(f=re.escape(field))) for form in DISPATCH_FORMS] - for root in roots: - for file in sources: - if file.suffix not in suffixes or not file.path.startswith(root.rstrip("/") + "/"): - continue - for index, form in enumerate(forms): - for match in form.finditer(file.text): - tokens = QUOTED.findall(match.group(1)) if index == MEMBERSHIP_FORM_INDEX else list(match.groups()) - for token in tokens: - if token == "": - continue # ``?? ""`` and ``or ""`` clear the field; not a value - observed.setdefault(token, set()).add(file.path) - return observed + selected = [source for source in sources if source.suffix in suffixes + and any(source.path.startswith(root.rstrip('/') + '/') for root in roots)] + return collect_literal_uses(REPO_ROOT, field, selected) def check_literal_vocabularies(registry: dict[str, Any], sources: list[SourceFile]) -> None: @@ -358,17 +417,58 @@ def check_literal_vocabularies(registry: dict[str, Any], sources: list[SourceFil if not scan: continue observed = scan_literals(scan["field"], scan["roots"], scan["suffixes"], sources) - expected = set(vocabulary["values"]) + expected = quota_action_domain(registry) if name == 'effective_action' else set(vocabulary["values"]) unregistered = {value: sorted(files) for value, files in observed.items() if value not in expected} require(not unregistered, f"{name}: literals not in the registry (register them or use a registered value): {unregistered}") + if name == 'effective_action': + require(not observed, + f"{name}: bare action literals outside the owner: " + f"{ {value: sorted(paths) for value, paths in observed.items()} }; " + "import EffectiveAction or AgentScopeFrontierAction instead") variable_sourced = vocabulary.get("variable_sourced_values", {}) for value, producer in variable_sourced.items(): text = (REPO_ROOT / producer).read_text(encoding="utf-8", errors="replace") require(f'"{value}"' in text, f"{name}: variable-sourced value {value} is no longer produced by {producer}") - unused = sorted(expected - set(observed) - set(variable_sourced)) + owner_values_seen = { + value + for owner in vocabulary["owners"].values() + if owner + for value in owner_values(owner) + } + unused = sorted( + set(vocabulary['values']) - set(observed) - set(variable_sourced) - owner_values_seen + ) require(not unused, f"{name}: registry lists values no module carries: {unused}") +# --- bounded producer scan ---------------------------------------------------------- + + +def _producer_literals(field: str, source: SourceFile) -> set[str]: + # Compatibility helper for direct-form mutation fixtures. No enum definitions + # are supplied, so these tests cannot accidentally count owners as producers. + if source.suffix != '.py': + return set() + rows = scan_python_production(source, field=field, enums={}) + return set().union(*(row.values for row in rows)) + + +def check_producers(registry: dict[str, Any], sources: list[SourceFile]) -> list[str]: + unknown: list[str] = [] + for name, vocabulary in registry['vocabularies'].items(): + if 'producers' not in vocabulary: + continue # Other kernel families retain an explicit M0.5 coverage gap. + try: + rows = collect_production(REPO_ROOT, vocabulary, sources) + if name == 'turn_result_kind': + rows.extend(probe_turn_result_input_domain(vocabulary)) + field_domain = quota_action_domain(registry) if name == 'effective_action' else None + unknown.extend(validate_production(name, vocabulary, rows, field_domain=field_domain)) + except ValueError as error: + raise Drift(str(error)) from error + return sorted(set(unknown)) + + # --- relations, projections, schema versions ---------------------------------------- @@ -386,7 +486,11 @@ def resolve(member: str) -> None: resolve(member) for shared in registry["relations"]["shared_field_names"]: for slot in shared["slots"]: - if "vocabulary" in slot: + if "vocabularies" in slot: + require(shared['field'] == 'effective_action' and slot['slot'] == 'should_run.effective_action', + 'only the anchored should-run field has a composed action domain') + quota_action_domain(registry) + elif "vocabulary" in slot: require(slot["vocabulary"] in vocabularies, f"shared field slot names unknown vocabulary {slot['vocabulary']}") else: for value in slot["values"]: @@ -429,6 +533,47 @@ def check_schema_version_owners(registry: dict[str, Any], sources: list[SourceFi require(values == {entry["value"]}, f"schema version {name} carries {sorted(values)}; registry says {entry['value']}") +def check_scope_declarations(registry: dict[str, Any], inventory: dict[str, Any]) -> int: + """Validate explicit bounded-context exceptions and return semantic fork count. + + The raw inventory remains unchanged. A declaration can remove a known, + reviewed bounded-context reuse from the semantic budget only when every + defining module is named explicitly. Spelling or directory proximity never + infers a scope. + """ + declarations = registry["scope_declarations"] + forks = {entry["name"]: entry for entry in inventory["duplicate_definitions"]["multi_value_forks"]} + for name, declaration in declarations.items(): + require(SYMBOL_NAME.match(name) is not None, f"scope declaration name must be an identifier: {name}") + require(set(declaration) == {"kind", "contexts"}, f"{name}: scope declaration keys must be kind and contexts") + require(declaration["kind"] == "bounded_context", f"{name}: only bounded_context is supported") + require(name in forks, f"{name}: scope declaration does not resolve to a multi-value fork") + contexts = declaration["contexts"] + require(isinstance(contexts, list) and contexts, f"{name}: contexts must be a non-empty list") + context_ids: set[str] = set() + owner_modules: set[str] = set() + for context in contexts: + require(set(context) == {"id", "owner"}, f"{name}: each context must have id and owner") + context_id = context["id"] + require(isinstance(context_id, str) and VALUE_SHAPE.match(context_id) is not None, + f"{name}: context id must be lower snake_case: {context_id!r}") + require(context_id not in context_ids, f"{name}: duplicate context id {context_id}") + context_ids.add(context_id) + owner = context["owner"] + require(isinstance(owner, str) and OWNER_SHAPE.match(owner) is not None, + f"{name}: context owner must be module::Symbol: {owner!r}") + module, symbol = owner.split("::") + require(symbol == name, f"{name}: context owner symbol must be {name}, got {symbol}") + owner_modules.add(module) + require(len(owner_modules) == len(contexts), f"{name}: each context must have a distinct owner module") + defining_modules = {item["module"] for item in forks[name]["definitions"]} + require(owner_modules == defining_modules, + f"{name}: contexts must name every defining module exactly once; " + f"declared={sorted(owner_modules)} actual={sorted(defining_modules)}") + undeclared = set(forks) - set(declarations) + return len(undeclared) + + # --- ratchets ----------------------------------------------------------------------- @@ -441,7 +586,7 @@ def check_retirement_budgets(registry: dict[str, Any], sources: list[SourceFile] (".py", "python_module_budget", RETIREMENT_ANCHOR[field][0]), (".ts", "typescript_module_budget", RETIREMENT_ANCHOR[field][1]), ): - actual = sum(1 for file in sources if file.suffix == suffix and field in file.text) + actual = count_identifier_modules(field, suffix, sources) require(actual <= budgets[key], f"legacy field {field} grew to {actual} {suffix} modules; budget is {budgets[key]}") require( budgets[key] == anchored, @@ -452,6 +597,23 @@ def check_retirement_budgets(registry: dict[str, Any], sources: list[SourceFile] return report +def count_identifier_modules(field: str, suffix: str, sources: list[SourceFile]) -> int: + """Count modules containing the standalone field token. + + This is intentionally a conservative lexical metric. It removes the known + ``goal_boundary_repair`` false positive without claiming to prove that every + remaining occurrence is a reader or that computed accesses are absent. + """ + pattern = re.compile( + rf"(? str: entry = registry["dual_runtime_twins"] require(entry["root"] == TWIN_ROOT_ANCHOR, "dual_runtime_twins root differs from TWIN_ROOT_ANCHOR") @@ -463,22 +625,21 @@ def check_dual_runtime_twins(registry: dict[str, Any]) -> str: def check_inventory(registry: dict[str, Any], sources: list[SourceFile]) -> tuple[dict[str, Any], str]: - inventory_path = REPO_ROOT / registry["inventory"] - committed = inventory_path.read_text(encoding="utf-8") inventory = build_inventory(REPO_ROOT, sources=sources) require(inventory["schema_version"] == INVENTORY_SCHEMA_VERSION, "inventory schema drift") - require(render_inventory(inventory) == committed, f"{registry['inventory']} is stale; from the repository root run uv run python scripts/generate_semantic_inventory.py and commit the result") + semantic_multi_value_forks = check_scope_declarations(registry, inventory) ratchets = registry["inventory_ratchets"] summary = inventory["summary"] parts = [] for key in RATCHET_KEYS: - require(summary[key] <= ratchets[key], f"inventory {key} grew to {summary[key]}; budget is {ratchets[key]}") + actual = semantic_multi_value_forks if key == "multi_value_forks_semantic" else summary[key] + require(actual <= ratchets[key], f"inventory {key} grew to {actual}; budget is {ratchets[key]}") require( ratchets[key] == BUDGET_ANCHOR[key], f"inventory {key} budget is {ratchets[key]} but BUDGET_ANCHOR pins {BUDGET_ANCHOR[key]}; " "the registry and the anchor move together in one diff (see BUDGET_ANCHOR in this smoke)", ) - parts.append(f"{key}={summary[key]}/{ratchets[key]}") + parts.append(f"{key}={actual}/{ratchets[key]}") return inventory, " ".join(parts) @@ -488,7 +649,14 @@ def main() -> int: sources = load_sources(REPO_ROOT) inventory, ratchets = check_inventory(registry, sources) check_owned_vocabularies(registry, inventory) + for path, expected in build_artifacts().items(): + require( + path.is_file() and path.read_text(encoding="utf-8") == expected, + f"{path.relative_to(REPO_ROOT)} is stale; " + "run uv run python scripts/generate_semantic_bindings.py and commit the result", + ) check_literal_vocabularies(registry, sources) + unknown_producers = check_producers(registry, sources) check_relations(registry) check_projections(registry) check_schema_version_owners(registry, sources) @@ -499,6 +667,12 @@ def main() -> int: print(" " + ratchets) print(" " + " ".join(budgets)) print(" " + twins) + print(f" unresolved_producer_sites={len(unknown_producers)} (not proven safe)") + uncovered = [name for name, v in registry['vocabularies'].items() if v['tier'] == 'kernel' and 'producers' not in v] + print(f" kernel_producer_coverage_pending={','.join(uncovered)}") + if '--report' in sys.argv[1:]: + for site in unknown_producers: + print(f" unknown_producer: {site}") return 0 diff --git a/loopx/capabilities/pr_review_queue/README.md b/loopx/capabilities/pr_review_queue/README.md index 9ed96ef5b7..91d593af03 100644 --- a/loopx/capabilities/pr_review_queue/README.md +++ b/loopx/capabilities/pr_review_queue/README.md @@ -351,7 +351,7 @@ Use this repair map when a check fails: | --- | --- | --- | --- | | `Sign-off` | One commit in the PR range lacks a valid DCO trailer | Add `Signed-off-by` to every affected commit with `git commit --amend -s` or an equivalent history repair; verify the full range | Signing only the newest commit | | semantic smoke: unregistered value | A recognised carrier/field form introduced a value outside the registry | Reuse the existing owner value, or add the value with its owner, slot, scope, tests, and RFC evidence | Registering an unrelated string to silence the error | -| semantic smoke: stale inventory | The committed generated map no longer matches the indexed source tree | Stage intended source paths, run `uv run python scripts/generate_semantic_inventory.py`, then `--check` | Editing counts by hand or including private/untracked files | +| optional inventory report: stale or missing | A developer-requested export differs from the current tree; semantic CI does not read it | Rerun `uv run python scripts/generate_semantic_inventory.py --output ` if the report is needed; semantic failures require fixing the reported rule | Committing the census, editing counts by hand, or replacing the full-tree scan with a diff-only scan | | semantic smoke: owner/parity | A defining symbol or Python/TypeScript value set diverged | Restore the single owner or deliberately update both runtime owners with parity evidence | Adding a second silent authority | | semantic smoke: projection | A source value is unmapped, mapped to the wrong target, or should be rejected explicitly | Update the declared mapping and executable owner together, then test the boundary case | Deleting a source value without compatibility analysis | | semantic smoke: budget/anchor | Measured debt grew or the guard was weakened | Fix the underlying duplicate/coverage issue and lower a budget only when the measured debt really fell | Raising the budget, narrowing the scan root, or renaming to hide drift | diff --git a/loopx/cli_commands/quota.py b/loopx/cli_commands/quota.py index 1075fa5100..fb4b5a3c62 100644 --- a/loopx/cli_commands/quota.py +++ b/loopx/cli_commands/quota.py @@ -1,4 +1,5 @@ from __future__ import annotations +from ..control_plane.quota.effective_action import EffectiveAction import argparse from collections.abc import Callable, Mapping @@ -226,7 +227,7 @@ def _apply_requested_quota_action_selection_preflight( pending_selection_workspace_repair_qualified = ( selection_binding == "pending_action_selection" and payload.get("workspace_repair_allowed") is True - and payload.get("effective_action") == "agent_workspace_repair" + and payload.get("effective_action") == EffectiveAction.AGENT_WORKSPACE_REPAIR.value and execution_obligation.get("kind") == "agent_workspace_repair" and execution_obligation.get("must_attempt_work") is True and agent_channel.get("must_attempt") is True @@ -276,7 +277,7 @@ def _apply_requested_quota_action_selection_preflight( "ok": False, "decision": "skip", "should_run": False, - "effective_action": error_code, + "effective_action": EffectiveAction.QUOTA_SKIP.value, "state": error_code, "waiting_on": "codex", "status": error_code, @@ -562,7 +563,7 @@ def handle_quota_command( turn_instance_id=heartbeat_turn_id, ) if ( - payload.get("effective_action") == "monitor_quiet_skip" + payload.get("effective_action") == EffectiveAction.MONITOR_QUIET_SKIP.value or existing_stall is not None ): poll = record_quota_monitor_poll( diff --git a/loopx/cli_commands/quota_scheduler_followup.py b/loopx/cli_commands/quota_scheduler_followup.py index 84e518b8e8..0e9fbef4ea 100644 --- a/loopx/cli_commands/quota_scheduler_followup.py +++ b/loopx/cli_commands/quota_scheduler_followup.py @@ -1,4 +1,5 @@ from __future__ import annotations +from ..control_plane.quota.effective_action import EffectiveAction import argparse from collections.abc import Callable, Mapping @@ -181,7 +182,7 @@ def build_scheduler_followup_payload( turn_instance_id and receipt_todo_id is None and receipt_replan_id is not None - and before_decision.get("effective_action") == "heartbeat_settled_skip" + and before_decision.get("effective_action") == EffectiveAction.HEARTBEAT_SETTLED_SKIP.value ): return { "ok": True, diff --git a/loopx/cli_commands/turn.py b/loopx/cli_commands/turn.py index 29adcdb506..e4ccb78e33 100644 --- a/loopx/cli_commands/turn.py +++ b/loopx/cli_commands/turn.py @@ -1,4 +1,5 @@ from __future__ import annotations +from ..control_plane.quota.effective_action import EffectiveAction import argparse import json @@ -163,7 +164,7 @@ def handle_turn_command( args.turn_command == "run-once" and args.host == "codex-cli" and not resume_requested - and turn_envelope.get("effective_action") != "governed_capability_intent" + and turn_envelope.get("effective_action") != EffectiveAction.GOVERNED_CAPABILITY_INTENT.value ): session_binding = codex_cli_session_binding(runtime_root, turn_envelope) payload = build_loopx_turn_plan( diff --git a/loopx/control_plane/agents/agent_scope_frontier.generated.ts b/loopx/control_plane/agents/agent_scope_frontier.generated.ts new file mode 100644 index 0000000000..80a14b8ec7 --- /dev/null +++ b/loopx/control_plane/agents/agent_scope_frontier.generated.ts @@ -0,0 +1,17 @@ +// Generated by scripts/generate_semantic_bindings.py; do not edit. +// Value owner: loopx/control_plane/agents/agent_scope_frontier.py::AgentScopeFrontierAction + +export const AGENT_SCOPE_FRONTIER_ACTIONS = [ + "agent_scope_exhausted", + "agent_scope_wait", + "reassignment_required", + "successor_replan_required", +] as const; +export type AgentScopeFrontierActionValue = (typeof AGENT_SCOPE_FRONTIER_ACTIONS)[number]; + +export const AgentScopeFrontierAction = { + AGENT_SCOPE_EXHAUSTED: AGENT_SCOPE_FRONTIER_ACTIONS[0], + AGENT_SCOPE_WAIT: AGENT_SCOPE_FRONTIER_ACTIONS[1], + REASSIGNMENT_REQUIRED: AGENT_SCOPE_FRONTIER_ACTIONS[2], + SUCCESSOR_REPLAN_REQUIRED: AGENT_SCOPE_FRONTIER_ACTIONS[3], +} as const; diff --git a/loopx/control_plane/agents/agent_scope_frontier.py b/loopx/control_plane/agents/agent_scope_frontier.py index ec0e97bc9a..0dec435cfd 100644 --- a/loopx/control_plane/agents/agent_scope_frontier.py +++ b/loopx/control_plane/agents/agent_scope_frontier.py @@ -1,10 +1,11 @@ from __future__ import annotations from enum import Enum +from collections.abc import Mapping from typing import Any -AGENT_SCOPE_FRONTIER_SCHEMA_VERSION = "agent_scope_frontier_v0" +AGENT_SCOPE_FRONTIER_SCHEMA_VERSION = "agent_scope_frontier_v1" AGENT_LANE_FRONTIER_HINT_SCHEMA_VERSION = "agent_lane_frontier_hint_v0" @@ -29,6 +30,11 @@ def agent_scope_frontier_action(value: Any) -> AgentScopeFrontierAction | None: return None +def read_frontier_action(payload: Mapping[str, Any]) -> str: + """Read the canonical action, retaining the persisted v0 alias as fallback.""" + return str(payload.get("action") or payload.get("effective_action") or "") + + def build_agent_scope_frontier_payload( *, agent_id: str, @@ -41,11 +47,18 @@ def build_agent_scope_frontier_payload( requires_replan: bool = False, extra_fields: dict[str, Any] | None = None, ) -> dict[str, Any]: + reserved = {"schema_version", "action", "effective_action"}.intersection( + extra_fields or {} + ) + if reserved: + raise ValueError( + "agent-scope frontier extra_fields contain reserved keys: " + + ", ".join(sorted(reserved)) + ) payload: dict[str, Any] = { "schema_version": AGENT_SCOPE_FRONTIER_SCHEMA_VERSION, "agent_id": agent_id, "action": action.value, - "effective_action": action.value, "blocks_delivery": True, "quiet_noop_allowed": quiet_noop_allowed, "spend_policy": spend_policy, diff --git a/loopx/control_plane/effect_program.py b/loopx/control_plane/effect_program.py index c9d862f460..8c76270ca9 100644 --- a/loopx/control_plane/effect_program.py +++ b/loopx/control_plane/effect_program.py @@ -117,7 +117,7 @@ class EffectInterpretation: class EffectObservation: decision: str should_run: bool - effective_action: str + effective_action: str | None recommended_action: str action_portfolio: Mapping[str, Any] | None = None planning_horizon: Mapping[str, Any] | None = None @@ -686,7 +686,11 @@ def _effect_turn_from_payload(payload: Any) -> EffectTurn: observation=EffectObservation( decision=str(observation.get("decision") or ""), should_run=observation.get("should_run") is True, - effective_action=str(observation.get("effective_action") or ""), + effective_action=( + str(observation["effective_action"]) + if observation.get("effective_action") is not None + else None + ), recommended_action=str(observation.get("recommended_action") or ""), action_portfolio=( dict(observation["action_portfolio"]) @@ -797,7 +801,7 @@ def interpret_turn_result_packet( agent_id: str | None = None, capabilities: Sequence[str] = (), ) -> EffectTurn: - """Map an existing `loopx_turn_result_v0` packet onto canonical slots.""" + """Project a Turn verdict; the TS owner emits no quota action (None).""" return _effect_turn_from_payload( effect_runtime_result( "effect.interpret_turn_result", diff --git a/loopx/control_plane/effect_program.ts b/loopx/control_plane/effect_program.ts index 48089368ee..d27d934d16 100644 --- a/loopx/control_plane/effect_program.ts +++ b/loopx/control_plane/effect_program.ts @@ -44,10 +44,13 @@ export interface EffectInterpretation { cadence_class: string | null; } -export interface EffectObservation { +export interface EffectObservation< + Decision extends string, + Action extends string | null = string, +> { decision: Decision; should_run: boolean; - effective_action: string; + effective_action: Action; recommended_action: string; action_portfolio: JsonObject | null; planning_horizon: JsonObject | null; @@ -63,10 +66,14 @@ export interface EffectNext { failure_cli_args: readonly string[]; } -export interface EffectTurn { +export interface EffectTurn< + Context, + Decision extends string, + Action extends string | null = string, +> { request: EffectRequest; interpretation: EffectInterpretation; - observation: EffectObservation; + observation: EffectObservation; next_effect: EffectNext; } @@ -343,7 +350,7 @@ export function interpretTurnResultPacket( agent_id?: string | null; capabilities?: readonly string[]; } = {}, -): EffectTurn { +): EffectTurn { const packet = asObject(packetValue); const scheduler = asObject(packet.scheduler_hint); const codexApp = asObject(scheduler.codex_app); @@ -376,7 +383,9 @@ export function interpretTurnResultPacket( observation: { decision: resultKind, should_run: false, - effective_action: truthyString(packet.effective_action) || resultKind, + // A host result carries a verdict, not a new quota decision. Ignore any + // host-supplied action and keep the no-action wire representation explicit. + effective_action: null, recommended_action: truthyString(packet.recommended_action) || "settle the turn receipt", action_portfolio: null, diff --git a/loopx/control_plane/quota/decision_summary.py b/loopx/control_plane/quota/decision_summary.py index 4735d17654..fb1635348b 100644 --- a/loopx/control_plane/quota/decision_summary.py +++ b/loopx/control_plane/quota/decision_summary.py @@ -1,4 +1,5 @@ from __future__ import annotations +from .effective_action import EffectiveAction from dataclasses import dataclass from typing import Any, TypedDict @@ -6,7 +7,6 @@ from ...state_projection import actions_are_projection_aligned from ..goals.contract_health import project_contract_health_for_goal from ..goals.goal_frontier import ( - AUTONOMOUS_REPLAN_REQUIRED_MODE, autonomous_replan_decision_allowed, goal_frontier_is_terminal_no_followup, ) @@ -293,7 +293,7 @@ def resolve_quota_run_decision( normal_delivery_allowed = False recovery_delivery_allowed = False should_run = True - effective_action = AUTONOMOUS_REPLAN_REQUIRED_MODE + effective_action = EffectiveAction.AUTONOMOUS_REPLAN_REQUIRED.value reason = ( "autonomous replan obligation is selected before monitor quiet " "or agent-scope wait classification" @@ -318,7 +318,7 @@ def resolve_quota_run_decision( capability_repair_allowed = False workspace_repair_allowed = False should_run = False - effective_action = "terminal_no_followup" + effective_action = EffectiveAction.TERMINAL_NO_FOLLOWUP.value reason = ( "validated closure evidence derives terminal no-follow-up from " "complete todo sources and an empty frontier; stop recurring " @@ -327,7 +327,7 @@ def resolve_quota_run_decision( if automation_prompt_upgrade_required and not terminal_no_followup: should_run = False - effective_action = "automation_prompt_upgrade_required" + effective_action = EffectiveAction.AUTOMATION_PROMPT_UPGRADE_REQUIRED.value elif inbox_reply_due: should_run = True normal_delivery_allowed = True @@ -335,7 +335,7 @@ def resolve_quota_run_decision( self_repair_allowed = False capability_repair_allowed = False workspace_repair_allowed = False - effective_action = "lark_inbox_reply_due" + effective_action = EffectiveAction.LARK_INBOX_REPLY_DUE.value reason = ( "a direct Lark question, bot mention, or verified reply to the bot " "is pending reply" @@ -347,7 +347,7 @@ def resolve_quota_run_decision( self_repair_allowed = False capability_repair_allowed = False workspace_repair_allowed = False - effective_action = "operator_inbox_material_review_due" + effective_action = EffectiveAction.OPERATOR_INBOX_MATERIAL_REVIEW_DUE.value reason = ( "captured unaddressed operator-inbox material is pending bounded review" ) @@ -386,29 +386,29 @@ def quota_effective_action( quota: dict[str, Any], ) -> str: if normal_delivery_allowed: - return "normal_run" + return EffectiveAction.NORMAL_RUN.value if recovery_delivery_allowed: - return "outcome_floor_recovery" + return EffectiveAction.OUTCOME_FLOOR_RECOVERY.value if workspace_repair_allowed: - return "agent_workspace_repair" + return EffectiveAction.AGENT_WORKSPACE_REPAIR.value if self_repair_allowed: repair_action = ( stall_self_repair.get("effective_action") if isinstance(stall_self_repair, dict) else None ) - return str(repair_action or "control_plane_repair") + return str(repair_action or EffectiveAction.CONTROL_PLANE_REPAIR.value) if capability_repair_allowed: - return "capability_bridge_repair" + return EffectiveAction.CAPABILITY_BRIDGE_REPAIR.value if state == "operator_gate": - return "operator_gate_notify" + return EffectiveAction.OPERATOR_GATE_NOTIFY.value if state == "blocked_health": - return "blocked_health" + return EffectiveAction.BLOCKED_HEALTH.value if state == "throttled": - return "throttled_skip" + return EffectiveAction.THROTTLED_SKIP.value if state in {"focus_wait", "waiting"} or quota.get("focus_wait"): - return "blocked_wait" - return "quota_skip" + return EffectiveAction.BLOCKED_WAIT.value + return EffectiveAction.QUOTA_SKIP.value def _task_orchestration_effective_action( @@ -424,18 +424,18 @@ def _task_orchestration_effective_action( and str(contract.get("execution_state") or "ready") == "ready" and should_run and normal_delivery_allowed - and effective_action == "normal_run" + and effective_action == EffectiveAction.NORMAL_RUN.value ): if contract.get("mode") == "adaptive": return ( - "coordinate_task_bundle", + EffectiveAction.COORDINATE_TASK_BUNDLE.value, ( "the task coordinator may use admitted child lanes before its " "own worker-lane delivery" ), ) return ( - "coordinate_task_bundle", + EffectiveAction.COORDINATE_TASK_BUNDLE.value, ( "the explicitly selected task coordinator must activate or resume " "eligible peer lanes before doing its own worker-lane delivery" diff --git a/loopx/control_plane/quota/effective_action.generated.ts b/loopx/control_plane/quota/effective_action.generated.ts new file mode 100644 index 0000000000..e4126cecf7 --- /dev/null +++ b/loopx/control_plane/quota/effective_action.generated.ts @@ -0,0 +1,76 @@ +// Generated by scripts/generate_semantic_bindings.py; do not edit. +// Value owner: loopx/control_plane/quota/effective_action.py::EffectiveAction + +export const EFFECTIVE_ACTIONS = [ + "agent_monitor_only", + "agent_workspace_repair", + "automation_prompt_upgrade_required", + "autonomous_replan_required", + "boundary_projection_repair", + "capability_bridge_repair", + "control_plane_health_repair", + "control_plane_projection_repair", + "coordinate_task_bundle", + "external_evidence_observe", + "governed_capability_intent", + "heartbeat_receipt_write_failed", + "heartbeat_settled_skip", + "lark_inbox_reply_due", + "monitor_due", + "monitor_quiet_skip", + "normal_run", + "operator_inbox_material_review_due", + "outcome_floor_recovery", + "peer_coordination_blocked", + "quota_skip", + "runtime_user_gate_projection_repair", + "scoped_user_gate_fallback", + "state_projection_gap_repair", + "terminal_no_followup", + "todo_decision_scope_projection_repair", + "unsettled_host_turn_recovery", + "blocked_health", + "blocked_wait", + "control_plane_repair", + "operator_gate_notify", + "throttled_skip", +] as const; +export type EffectiveActionValue = (typeof EFFECTIVE_ACTIONS)[number]; + +export const EffectiveAction = { + AGENT_MONITOR_ONLY: EFFECTIVE_ACTIONS[0], + AGENT_WORKSPACE_REPAIR: EFFECTIVE_ACTIONS[1], + AUTOMATION_PROMPT_UPGRADE_REQUIRED: EFFECTIVE_ACTIONS[2], + AUTONOMOUS_REPLAN_REQUIRED: EFFECTIVE_ACTIONS[3], + BOUNDARY_PROJECTION_REPAIR: EFFECTIVE_ACTIONS[4], + CAPABILITY_BRIDGE_REPAIR: EFFECTIVE_ACTIONS[5], + CONTROL_PLANE_HEALTH_REPAIR: EFFECTIVE_ACTIONS[6], + CONTROL_PLANE_PROJECTION_REPAIR: EFFECTIVE_ACTIONS[7], + COORDINATE_TASK_BUNDLE: EFFECTIVE_ACTIONS[8], + EXTERNAL_EVIDENCE_OBSERVE: EFFECTIVE_ACTIONS[9], + GOVERNED_CAPABILITY_INTENT: EFFECTIVE_ACTIONS[10], + HEARTBEAT_RECEIPT_WRITE_FAILED: EFFECTIVE_ACTIONS[11], + HEARTBEAT_SETTLED_SKIP: EFFECTIVE_ACTIONS[12], + LARK_INBOX_REPLY_DUE: EFFECTIVE_ACTIONS[13], + MONITOR_DUE: EFFECTIVE_ACTIONS[14], + MONITOR_QUIET_SKIP: EFFECTIVE_ACTIONS[15], + NORMAL_RUN: EFFECTIVE_ACTIONS[16], + OPERATOR_INBOX_MATERIAL_REVIEW_DUE: EFFECTIVE_ACTIONS[17], + OUTCOME_FLOOR_RECOVERY: EFFECTIVE_ACTIONS[18], + PEER_COORDINATION_BLOCKED: EFFECTIVE_ACTIONS[19], + QUOTA_SKIP: EFFECTIVE_ACTIONS[20], + RUNTIME_USER_GATE_PROJECTION_REPAIR: EFFECTIVE_ACTIONS[21], + SCOPED_USER_GATE_FALLBACK: EFFECTIVE_ACTIONS[22], + STATE_PROJECTION_GAP_REPAIR: EFFECTIVE_ACTIONS[23], + TERMINAL_NO_FOLLOWUP: EFFECTIVE_ACTIONS[24], + TODO_DECISION_SCOPE_PROJECTION_REPAIR: EFFECTIVE_ACTIONS[25], + UNSETTLED_HOST_TURN_RECOVERY: EFFECTIVE_ACTIONS[26], + BLOCKED_HEALTH: EFFECTIVE_ACTIONS[27], + BLOCKED_WAIT: EFFECTIVE_ACTIONS[28], + CONTROL_PLANE_REPAIR: EFFECTIVE_ACTIONS[29], + OPERATOR_GATE_NOTIFY: EFFECTIVE_ACTIONS[30], + THROTTLED_SKIP: EFFECTIVE_ACTIONS[31], +} as const; + +import type { AgentScopeFrontierActionValue } from "../agents/agent_scope_frontier.generated.ts"; +export type QuotaEffectiveActionValue = EffectiveActionValue | AgentScopeFrontierActionValue; diff --git a/loopx/control_plane/quota/effective_action.py b/loopx/control_plane/quota/effective_action.py new file mode 100644 index 0000000000..a7eefdae83 --- /dev/null +++ b/loopx/control_plane/quota/effective_action.py @@ -0,0 +1,45 @@ +"""Canonical value domain for the decision-slot effective action. + +The Turn Envelope still carries a string for wire compatibility. This enum is +the owner of the finite value domain; callers may serialize ``.value`` while +the semantic drift smoke checks that new decision values are deliberate. +""" + +from __future__ import annotations + +from enum import Enum + + +class EffectiveAction(str, Enum): + AGENT_MONITOR_ONLY = "agent_monitor_only" + AGENT_WORKSPACE_REPAIR = "agent_workspace_repair" + AUTOMATION_PROMPT_UPGRADE_REQUIRED = "automation_prompt_upgrade_required" + AUTONOMOUS_REPLAN_REQUIRED = "autonomous_replan_required" + BOUNDARY_PROJECTION_REPAIR = "boundary_projection_repair" + CAPABILITY_BRIDGE_REPAIR = "capability_bridge_repair" + CONTROL_PLANE_HEALTH_REPAIR = "control_plane_health_repair" + CONTROL_PLANE_PROJECTION_REPAIR = "control_plane_projection_repair" + COORDINATE_TASK_BUNDLE = "coordinate_task_bundle" + EXTERNAL_EVIDENCE_OBSERVE = "external_evidence_observe" + GOVERNED_CAPABILITY_INTENT = "governed_capability_intent" + HEARTBEAT_RECEIPT_WRITE_FAILED = "heartbeat_receipt_write_failed" + HEARTBEAT_SETTLED_SKIP = "heartbeat_settled_skip" + LARK_INBOX_REPLY_DUE = "lark_inbox_reply_due" + MONITOR_DUE = "monitor_due" + MONITOR_QUIET_SKIP = "monitor_quiet_skip" + NORMAL_RUN = "normal_run" + OPERATOR_INBOX_MATERIAL_REVIEW_DUE = "operator_inbox_material_review_due" + OUTCOME_FLOOR_RECOVERY = "outcome_floor_recovery" + PEER_COORDINATION_BLOCKED = "peer_coordination_blocked" + QUOTA_SKIP = "quota_skip" + RUNTIME_USER_GATE_PROJECTION_REPAIR = "runtime_user_gate_projection_repair" + SCOPED_USER_GATE_FALLBACK = "scoped_user_gate_fallback" + STATE_PROJECTION_GAP_REPAIR = "state_projection_gap_repair" + TERMINAL_NO_FOLLOWUP = "terminal_no_followup" + TODO_DECISION_SCOPE_PROJECTION_REPAIR = "todo_decision_scope_projection_repair" + UNSETTLED_HOST_TURN_RECOVERY = "unsettled_host_turn_recovery" + BLOCKED_HEALTH = "blocked_health" + BLOCKED_WAIT = "blocked_wait" + CONTROL_PLANE_REPAIR = "control_plane_repair" + OPERATOR_GATE_NOTIFY = "operator_gate_notify" + THROTTLED_SKIP = "throttled_skip" diff --git a/loopx/control_plane/quota/heartbeat_receipt.py b/loopx/control_plane/quota/heartbeat_receipt.py index 26b88a7a87..f902b35fd9 100644 --- a/loopx/control_plane/quota/heartbeat_receipt.py +++ b/loopx/control_plane/quota/heartbeat_receipt.py @@ -1,4 +1,5 @@ from __future__ import annotations +from .effective_action import EffectiveAction import json from collections.abc import Mapping @@ -462,7 +463,7 @@ def fail_heartbeat_receipt( "ok": False, "decision": "skip", "should_run": False, - "effective_action": "heartbeat_receipt_write_failed", + "effective_action": EffectiveAction.HEARTBEAT_RECEIPT_WRITE_FAILED.value, "state": "blocked_health", "waiting_on": "codex", "reason": reason, diff --git a/loopx/control_plane/quota/host_poll_receipts.py b/loopx/control_plane/quota/host_poll_receipts.py index 83e9c79c69..5657699cd9 100644 --- a/loopx/control_plane/quota/host_poll_receipts.py +++ b/loopx/control_plane/quota/host_poll_receipts.py @@ -12,6 +12,7 @@ """ from __future__ import annotations +from .effective_action import EffectiveAction import json import os @@ -72,7 +73,7 @@ def record_host_poll_receipt( terminal_state = frontier.get("terminal_state") if isinstance(frontier, dict) else None terminal = bool( decision.get("should_run") is False - and decision.get("effective_action") == "terminal_no_followup" + and decision.get("effective_action") == EffectiveAction.TERMINAL_NO_FOLLOWUP.value and isinstance(terminal_state, dict) and terminal_state.get("kind") == "no_followup" ) diff --git a/loopx/control_plane/quota/live_decision.py b/loopx/control_plane/quota/live_decision.py index c9ac6427b6..62a5a6ef62 100644 --- a/loopx/control_plane/quota/live_decision.py +++ b/loopx/control_plane/quota/live_decision.py @@ -1,4 +1,5 @@ from __future__ import annotations +from .effective_action import EffectiveAction import shlex from collections.abc import Callable, Mapping, Sequence @@ -191,7 +192,7 @@ def _apply_pending_capability_intent_precedence( "decision": "run", "should_run": True, "state": "eligible", - "effective_action": "governed_capability_intent", + "effective_action": EffectiveAction.GOVERNED_CAPABILITY_INTENT.value, "actionable_by_codex": True, "normal_delivery_allowed": False, "recovery_delivery_allowed": False, diff --git a/loopx/control_plane/quota/monitor_poll_commit.ts b/loopx/control_plane/quota/monitor_poll_commit.ts index 5a80f8dd4c..19ea7e1750 100644 --- a/loopx/control_plane/quota/monitor_poll_commit.ts +++ b/loopx/control_plane/quota/monitor_poll_commit.ts @@ -1,3 +1,5 @@ +import { EffectiveAction, type QuotaEffectiveActionValue } from "./effective_action.generated.ts"; +import { AgentScopeFrontierAction } from "../agents/agent_scope_frontier.generated.ts"; import { createHash } from "node:crypto"; import { access, readFile, rm } from "node:fs/promises"; import { basename, dirname, join, resolve } from "node:path"; @@ -454,9 +456,12 @@ function exactBlockedWait(decision: MonitorDecision): JsonObject | null { } function blockedSuccessorAllowed(decision: MonitorDecision): boolean { - return ["agent_scope_wait", "monitor_quiet_skip"].includes( + return ([ + AgentScopeFrontierAction.AGENT_SCOPE_WAIT, EffectiveAction.MONITOR_QUIET_SKIP, + ] satisfies readonly QuotaEffectiveActionValue[] as readonly string[]).includes( decision.effective_action ?? "", - ) && !decision.should_run && !decision.requires_user_action && exactBlockedWait(decision) !== null; + ) && !decision.should_run && + !decision.requires_user_action && exactBlockedWait(decision) !== null; } function externalMonitorAllowed(decision: MonitorDecision): boolean { @@ -510,7 +515,7 @@ function admission(request: MonitorRequest): Admission { const external = externalMonitorAllowed(request.decision); const due = dueMonitorAllowed(request.decision, request.observation); if ( - request.decision.effective_action !== "monitor_quiet_skip" && + request.decision.effective_action !== EffectiveAction.MONITOR_QUIET_SKIP && !external && !due && !blocked ) { throw new EffectRuntimeRequestError( diff --git a/loopx/control_plane/quota/projection_repair.py b/loopx/control_plane/quota/projection_repair.py index df427715aa..fb201de35c 100644 --- a/loopx/control_plane/quota/projection_repair.py +++ b/loopx/control_plane/quota/projection_repair.py @@ -1,4 +1,5 @@ from __future__ import annotations +from .effective_action import EffectiveAction import fnmatch from typing import Any @@ -108,7 +109,7 @@ def build_state_projection_gap_repair_hint( "source": "quota.should-run", "trigger": "state_projection_gap", "recommended_mode": "repair_state_projection_gap", - "effective_action": "state_projection_gap_repair", + "effective_action": EffectiveAction.STATE_PROJECTION_GAP_REPAIR.value, "allowed": True, "notify": "DONT_NOTIFY", "reason": ( @@ -204,7 +205,7 @@ def build_boundary_projection_repair_hint( "source": "quota.should-run", "trigger": "required_write_scope_missing_from_goal_boundary", "recommended_mode": "repair_boundary_projection", - "effective_action": "boundary_projection_repair", + "effective_action": EffectiveAction.BOUNDARY_PROJECTION_REPAIR.value, "blocked_action_scope": "boundary_projection", "allowed": True, "notify": "DONT_NOTIFY", diff --git a/loopx/control_plane/quota/settlement_cli.py b/loopx/control_plane/quota/settlement_cli.py index 9bc0acc544..4722bcdd74 100644 --- a/loopx/control_plane/quota/settlement_cli.py +++ b/loopx/control_plane/quota/settlement_cli.py @@ -1,6 +1,7 @@ """CLI rollout helpers for heartbeat settlement identity and receipt wiring.""" from __future__ import annotations +from .effective_action import EffectiveAction import argparse from collections.abc import Mapping @@ -193,7 +194,7 @@ def quota_rollout_settlement_binding( packet is only a diagnostic fallback when no concrete Todo is selected. """ - if payload.get("effective_action") == "unsettled_host_turn_recovery": + if payload.get("effective_action") == EffectiveAction.UNSETTLED_HOST_TURN_RECOVERY.value: # This Turn only repairs the preceding Turn's closeout. A concurrently # projected Todo or autonomous replan belongs to the post-recovery # decision and must not become this receipt's settlement identity. diff --git a/loopx/control_plane/quota/settlement_precedence.py b/loopx/control_plane/quota/settlement_precedence.py index c87173c6e1..f1cf97b179 100644 --- a/loopx/control_plane/quota/settlement_precedence.py +++ b/loopx/control_plane/quota/settlement_precedence.py @@ -1,4 +1,5 @@ from __future__ import annotations +from .effective_action import EffectiveAction from typing import Any, Protocol @@ -77,7 +78,7 @@ def apply_settled_replay_route_precedence( route.capability_repair_allowed = False route.workspace_repair_allowed = False route.should_run = False - route.effective_action = "heartbeat_settled_skip" + route.effective_action = EffectiveAction.HEARTBEAT_SETTLED_SKIP.value route.reason = HEARTBEAT_SETTLED_REPLAY_REASON route.replan_decision_allowed = False route.receipt_bound_replan_decision = False @@ -127,7 +128,7 @@ def apply_settled_replay_payload_precedence( "self_repair_allowed": False, "capability_repair_allowed": False, "workspace_repair_allowed": False, - "effective_action": "heartbeat_settled_skip", + "effective_action": EffectiveAction.HEARTBEAT_SETTLED_SKIP.value, "actionable_by_codex": False, "reason": reason, "requires_user_action": False, diff --git a/loopx/control_plane/quota/should_run.py b/loopx/control_plane/quota/should_run.py index a8f4a265ab..66f052a7c7 100644 --- a/loopx/control_plane/quota/should_run.py +++ b/loopx/control_plane/quota/should_run.py @@ -1,4 +1,5 @@ from __future__ import annotations +from .effective_action import EffectiveAction from collections.abc import Callable, Mapping from pathlib import Path @@ -173,7 +174,7 @@ def build_quota_paused_should_run_payload( } execution_obligation = _execution_obligation( should_run=False, - effective_action="quota_skip", + effective_action=EffectiveAction.QUOTA_SKIP.value, heartbeat_recommendation=heartbeat_recommendation, ) payload: dict[str, Any] = { @@ -188,7 +189,7 @@ def build_quota_paused_should_run_payload( "self_repair_allowed": False, "capability_repair_allowed": False, "workspace_repair_allowed": False, - "effective_action": "quota_skip", + "effective_action": EffectiveAction.QUOTA_SKIP.value, "actionable_by_codex": False, "reason": reason, "quota": quota, diff --git a/loopx/control_plane/quota/should_run_packet.py b/loopx/control_plane/quota/should_run_packet.py index ab7eb108d0..631a5c7619 100644 --- a/loopx/control_plane/quota/should_run_packet.py +++ b/loopx/control_plane/quota/should_run_packet.py @@ -1,5 +1,5 @@ from __future__ import annotations - +from .effective_action import EffectiveAction from collections.abc import Mapping from dataclasses import dataclass from pathlib import Path @@ -8,8 +8,6 @@ from ...long_task_cadence import reconcile_long_task_cadence_hint from ...state_projection import ( next_action_projection_warning, -) -from ...state_projection import ( state_action_projection_warning as build_state_action_projection_warning, ) from .. import compact_control_plane_policy @@ -28,6 +26,7 @@ _attach_agent_identity_contracts, ) from ..agents.capability_gate import missing_required_capabilities +from ..agents.agent_scope_frontier import read_frontier_action from ..goals.goal_frontier import ( AUTONOMOUS_REPLAN_REQUIRED_MODE, ) @@ -349,7 +348,7 @@ def _apply_agent_monitor_only_precedence( "self_repair_allowed": False, "capability_repair_allowed": False, "workspace_repair_allowed": False, - "effective_action": "monitor_due" if monitor_due else "monitor_quiet_skip", + "effective_action": EffectiveAction.MONITOR_DUE.value if monitor_due else EffectiveAction.MONITOR_QUIET_SKIP.value, "actionable_by_codex": monitor_due, "reason": reason, "blocked_action_scope": "advancement_work", @@ -397,7 +396,7 @@ def _apply_agent_monitor_only_precedence( "self_repair_allowed": False, "capability_repair_allowed": False, "workspace_repair_allowed": False, - "effective_action": "agent_monitor_only", + "effective_action": EffectiveAction.AGENT_MONITOR_ONLY.value, "actionable_by_codex": False, "reason": reason, "blocked_action_scope": "advancement_work", @@ -745,7 +744,7 @@ def _planning_projections( and prepared.workspace_guard and prepared.normal_delivery_allowed ) or bool( - route.effective_action == "boundary_projection_repair" + route.effective_action == EffectiveAction.BOUNDARY_PROJECTION_REPAIR.value and prepared.boundary_projection_repair ) projection_enabled = bool( @@ -893,7 +892,7 @@ def _resolve_quota_should_run_route( "spend_policy": external_evidence_observation.get("spend_policy") or heartbeat_recommendation.get("spend_policy"), } - effective_action = "external_evidence_observe" + effective_action = EffectiveAction.EXTERNAL_EVIDENCE_OBSERVE.value reason = "external evidence monitor requires read-only observation before quiet no-op" receipt_bound_monitor_settled = ( work_lane_contract_is_receipt_bound_monitor_settled( @@ -905,7 +904,7 @@ def _resolve_quota_should_run_route( recovery_allowed = False self_repair_allowed = False should_run = False - effective_action = "heartbeat_settled_skip" + effective_action = EffectiveAction.HEARTBEAT_SETTLED_SKIP.value reason = ( "the receipt-bound monitor poll and required settlement receipts are " "complete for this heartbeat turn; defer successor selection to a new turn" @@ -934,7 +933,7 @@ def _resolve_quota_should_run_route( if monitor_quiet_skip: normal_delivery_allowed = False should_run = False - effective_action = "monitor_quiet_skip" + effective_action = EffectiveAction.MONITOR_QUIET_SKIP.value reason = str( heartbeat_recommendation.get("reason") or "monitor-only polling has no material transition; skip delivery compute" @@ -1015,7 +1014,7 @@ def _resolve_quota_should_run_route( if agent_scope_frontier and agent_lane_frontier_hint: agent_scope_frontier["frontier_hint"] = agent_lane_frontier_hint if agent_scope_frontier: - frontier_action = str(agent_scope_frontier.get("effective_action") or "") + frontier_action = read_frontier_action(agent_scope_frontier) successor_replan_required = ( frontier_action == AgentScopeFrontierAction.SUCCESSOR_REPLAN_REQUIRED.value @@ -1040,7 +1039,7 @@ def _resolve_quota_should_run_route( prepared.task_orchestration_contract, effective_action=effective_action, ): - effective_action = PEER_COORDINATION_BLOCKED_ACTION + effective_action = EffectiveAction.PEER_COORDINATION_BLOCKED.value reason = ( "the explicitly selected peer task bundle is blocked and the " "coordinator has no in-scope runnable fallback; return control " diff --git a/loopx/control_plane/quota/slot_accounting.py b/loopx/control_plane/quota/slot_accounting.py index 6819f75d82..bc9088e840 100644 --- a/loopx/control_plane/quota/slot_accounting.py +++ b/loopx/control_plane/quota/slot_accounting.py @@ -1,4 +1,5 @@ from __future__ import annotations +from .effective_action import EffectiveAction import json from collections.abc import Callable, Iterable @@ -606,13 +607,13 @@ def build_quota_slot_preview_for_decision( ( before.get("state") == "operator_gate" or before.get("recovery_delivery_allowed") is True - or before.get("effective_action") == "outcome_floor_recovery" + or before.get("effective_action") == EffectiveAction.OUTCOME_FLOOR_RECOVERY.value ) and before.get("safe_bypass_allowed") is True ) self_repair_spend = before.get("effective_action") in self_repair_spend_actions capability_repair_spend = ( - before.get("effective_action") == "capability_bridge_repair" + before.get("effective_action") == EffectiveAction.CAPABILITY_BRIDGE_REPAIR.value and before.get("capability_repair_allowed") is True ) delivery_completion_run = delivery_completion_run or ( @@ -715,7 +716,7 @@ def build_quota_slot_preview_for_decision( } delivery_workspace_validated = bool(delivery_workspace) workspace_repair_no_spend = ( - before.get("effective_action") == "agent_workspace_repair" + before.get("effective_action") == EffectiveAction.AGENT_WORKSPACE_REPAIR.value and before.get("workspace_repair_allowed") is True and not delivery_workspace_validated ) @@ -745,13 +746,13 @@ def build_quota_slot_preview_for_decision( and ( settlement_identity is not None or not before.get("should_run") - or before.get("effective_action") == "external_evidence_observe" + or before.get("effective_action") == EffectiveAction.EXTERNAL_EVIDENCE_OBSERVE.value or ( - before.get("effective_action") == "agent_workspace_repair" + before.get("effective_action") == EffectiveAction.AGENT_WORKSPACE_REPAIR.value and delivery_workspace_validated ) ) - and before.get("effective_action") != "automation_prompt_upgrade_required" + and before.get("effective_action") != EffectiveAction.AUTOMATION_PROMPT_UPGRADE_REQUIRED.value and not safe_bypass_spend and str(before.get("state") or "") in {"waiting", "focus_wait", "operator_gate", "eligible"} ) diff --git a/loopx/control_plane/quota/spend_commit.ts b/loopx/control_plane/quota/spend_commit.ts index 95ea5e678f..484e37416f 100644 --- a/loopx/control_plane/quota/spend_commit.ts +++ b/loopx/control_plane/quota/spend_commit.ts @@ -1,3 +1,4 @@ +import { EffectiveAction, type EffectiveActionValue } from "./effective_action.generated.ts"; import { createHash } from "node:crypto"; import { basename, isAbsolute, join } from "node:path"; @@ -44,12 +45,12 @@ export const QUOTA_SPEND_SOURCES = [ ] as const; export type QuotaSpendSource = (typeof QUOTA_SPEND_SOURCES)[number]; -const SELF_REPAIR_SPEND_ACTIONS = new Set([ - "control_plane_health_repair", - "control_plane_projection_repair", - "state_projection_gap_repair", - "boundary_projection_repair", - "todo_decision_scope_projection_repair", +const SELF_REPAIR_SPEND_ACTIONS: ReadonlySet = new Set([ + EffectiveAction.CONTROL_PLANE_HEALTH_REPAIR, + EffectiveAction.CONTROL_PLANE_PROJECTION_REPAIR, + EffectiveAction.STATE_PROJECTION_GAP_REPAIR, + EffectiveAction.BOUNDARY_PROJECTION_REPAIR, + EffectiveAction.TODO_DECISION_SCOPE_PROJECTION_REPAIR, ]); type QuotaSpendCommitStatus = @@ -364,25 +365,25 @@ function spendDisposition(request: QuotaSpendCommitRequest): SpendDisposition { action !== null && SELF_REPAIR_SPEND_ACTIONS.has(action) && request.before.self_repair_allowed; const capabilityRepairSpend = request.before.should_run && - action === "capability_bridge_repair" && + action === EffectiveAction.CAPABILITY_BRIDGE_REPAIR && request.before.capability_repair_allowed; const eligibleSpend = request.before.should_run && request.before.state === "eligible" && - action !== "external_evidence_observe" && + action !== EffectiveAction.EXTERNAL_EVIDENCE_OBSERVE && !selfRepairSpend && !capabilityRepairSpend && !request.before.workspace_repair_allowed && !deliveryCompletionSpend; const safeBypassSpend = request.preview.safe_bypass_spend === true && ( request.before.state === "operator_gate" || request.before.recovery_delivery_allowed || - action === "outcome_floor_recovery" + action === EffectiveAction.OUTCOME_FLOOR_RECOVERY ) && request.before.safe_bypass_allowed; // A recovered settlement describes work that already happened. The current // frontier may now ask for capability or control-plane repair, but that later // projection cannot rewrite the attribution of the completed delivery. if (deliveryCompletionSpend) return "delivery_completion"; if (eligibleSpend) return "eligible"; - if (safeBypassSpend && action === "outcome_floor_recovery") { + if (safeBypassSpend && action === EffectiveAction.OUTCOME_FLOOR_RECOVERY) { return "outcome_floor_recovery"; } if (selfRepairSpend) return "control_plane_self_repair"; diff --git a/loopx/control_plane/quota/stall_repair.py b/loopx/control_plane/quota/stall_repair.py index 66463c8415..58f7568611 100644 --- a/loopx/control_plane/quota/stall_repair.py +++ b/loopx/control_plane/quota/stall_repair.py @@ -1,4 +1,5 @@ from __future__ import annotations +from .effective_action import EffectiveAction from typing import Any @@ -216,7 +217,7 @@ def build_runtime_capability_user_gate_repair_hint( "trigger": RUNTIME_CAPABILITY_USER_GATE_REPAIR_TRIGGER, "schema_version": "runtime_capability_user_gate_repair_v0", "recommended_mode": "repair_user_gate_projection", - "effective_action": "runtime_user_gate_projection_repair", + "effective_action": EffectiveAction.RUNTIME_USER_GATE_PROJECTION_REPAIR.value, "blocked_action_scope": "user_gate_projection", "allowed": True, "notify": "DONT_NOTIFY", @@ -297,7 +298,7 @@ def build_quota_stall_self_repair_hint( "source": "quota.should-run", "trigger": "health_blocker", "recommended_mode": "repair_control_plane_health", - "effective_action": "control_plane_health_repair", + "effective_action": EffectiveAction.CONTROL_PLANE_HEALTH_REPAIR.value, "allowed": True, "notify": "DONT_NOTIFY", "reason": ( @@ -334,7 +335,7 @@ def build_quota_stall_self_repair_hint( "source": "quota.should-run", "trigger": "waiting_without_owner_projection", "recommended_mode": "repair_waiting_projection", - "effective_action": "control_plane_projection_repair", + "effective_action": EffectiveAction.CONTROL_PLANE_PROJECTION_REPAIR.value, "allowed": True, "notify": "DONT_NOTIFY", "reason": ( diff --git a/loopx/control_plane/quota/task_orchestration.py b/loopx/control_plane/quota/task_orchestration.py index 646a8e2f1b..8e8312c13f 100644 --- a/loopx/control_plane/quota/task_orchestration.py +++ b/loopx/control_plane/quota/task_orchestration.py @@ -1,5 +1,5 @@ from __future__ import annotations - +from .effective_action import EffectiveAction from typing import Any from ..agents.agent_scope_frontier import AgentScopeFrontierAction @@ -22,7 +22,7 @@ AgentScopeFrontierAction.REASSIGNMENT_REQUIRED.value, } PEER_AGENT_ACTIVATION_CAPABILITY = "peer_agent_activation" -PEER_COORDINATION_BLOCKED_ACTION = "peer_coordination_blocked" +PEER_COORDINATION_BLOCKED_ACTION = EffectiveAction.PEER_COORDINATION_BLOCKED.value def task_orchestration_contract_is_actionable( @@ -94,7 +94,7 @@ def payload_work_lane_contract( recovery_allowed: bool, agent_scope_frontier: dict[str, Any] | None, ) -> dict[str, Any] | None: - if recovery_allowed and effective_action == "outcome_floor_recovery": + if recovery_allowed and effective_action == EffectiveAction.OUTCOME_FLOOR_RECOVERY.value: return None if not isinstance(work_lane_contract, dict): return work_lane_contract diff --git a/loopx/control_plane/quota/turn_envelope.ts b/loopx/control_plane/quota/turn_envelope.ts index e3d3c0f453..6756491cc7 100644 --- a/loopx/control_plane/quota/turn_envelope.ts +++ b/loopx/control_plane/quota/turn_envelope.ts @@ -1,3 +1,4 @@ +import { EffectiveAction } from "./effective_action.generated.ts"; import { createHash } from "node:crypto"; import { @@ -575,7 +576,7 @@ function actionProjection(payload: JsonObject, protocolActionFields: JsonObject) const interaction = object(payload.interaction_contract); const agentChannel = object(interaction.agent_channel); const cliChannel = object(interaction.cli_channel); - const capabilityIntent = payload.effective_action === "governed_capability_intent" + const capabilityIntent = payload.effective_action === EffectiveAction.GOVERNED_CAPABILITY_INTENT ? projectPendingCapabilityIntent(payload.pending_capability_intent) : null; // A governed capability action has already won the live decision. Stale // replan/host-reentry projections must not replace its exact command. diff --git a/loopx/control_plane/quota/unsettled_host_turn.py b/loopx/control_plane/quota/unsettled_host_turn.py index b867a36add..ef12753ae8 100644 --- a/loopx/control_plane/quota/unsettled_host_turn.py +++ b/loopx/control_plane/quota/unsettled_host_turn.py @@ -1,4 +1,5 @@ from __future__ import annotations +from .effective_action import EffectiveAction from collections.abc import Mapping from pathlib import Path @@ -227,7 +228,7 @@ def apply_unsettled_host_turn_recovery_if_required( "decision": "unsettled_host_turn_recovery", "should_run": True, "state": "eligible", - "effective_action": "unsettled_host_turn_recovery", + "effective_action": EffectiveAction.UNSETTLED_HOST_TURN_RECOVERY.value, "actionable_by_codex": True, "normal_delivery_allowed": False, "recovery_delivery_allowed": False, diff --git a/loopx/control_plane/scheduler/automation_liveness.py b/loopx/control_plane/scheduler/automation_liveness.py index e821c68592..ffd8dde899 100644 --- a/loopx/control_plane/scheduler/automation_liveness.py +++ b/loopx/control_plane/scheduler/automation_liveness.py @@ -1,4 +1,5 @@ from __future__ import annotations +from ..quota.effective_action import EffectiveAction from typing import Any @@ -64,7 +65,7 @@ def build_automation_liveness(payload: dict[str, Any]) -> dict[str, Any]: "next_trigger": "explicit quota resume with quota.compute > 0", "spend_policy": "no quota spend for paused automation shutdown", } - if effective_action == "agent_monitor_only": + if effective_action == EffectiveAction.AGENT_MONITOR_ONLY.value: return { **base, "keep_active": True, @@ -81,7 +82,7 @@ def build_automation_liveness(payload: dict[str, Any]) -> dict[str, Any]: ), "spend_policy": "no quota spend without a validated material transition", } - if effective_action == "terminal_no_followup": + if effective_action == EffectiveAction.TERMINAL_NO_FOLLOWUP.value: return { **base, "keep_active": False, @@ -99,7 +100,7 @@ def build_automation_liveness(payload: dict[str, Any]) -> dict[str, Any]: "spend_policy": "no quota spend for terminal automation shutdown", } if ( - effective_action == "monitor_quiet_skip" + effective_action == EffectiveAction.MONITOR_QUIET_SKIP.value or recommended_mode == "monitor_quiet_until_material_transition" ): return { @@ -115,7 +116,7 @@ def build_automation_liveness(payload: dict[str, Any]) -> dict[str, Any]: ), "spend_policy": "no quota spend for unchanged monitor-only polls", } - if effective_action == "heartbeat_settled_skip": + if effective_action == EffectiveAction.HEARTBEAT_SETTLED_SKIP.value: return { **base, "automation_action": "keep_active_quiet", @@ -126,7 +127,7 @@ def build_automation_liveness(payload: dict[str, Any]) -> dict[str, Any]: "next_trigger": "next heartbeat turn with a fresh turn identity", "spend_policy": "no quota spend for an already-settled heartbeat turn", } - if effective_action == "automation_prompt_upgrade_required": + if effective_action == EffectiveAction.AUTOMATION_PROMPT_UPGRADE_REQUIRED.value: return { **base, "automation_action": "repair_automation_prompt_identity", diff --git a/loopx/control_plane/testing/action_portfolio_scenarios.py b/loopx/control_plane/testing/action_portfolio_scenarios.py index f872ec855b..d7debbcbde 100644 --- a/loopx/control_plane/testing/action_portfolio_scenarios.py +++ b/loopx/control_plane/testing/action_portfolio_scenarios.py @@ -4,6 +4,7 @@ from typing import Any from ..quota.cli_projection import compact_quota_should_run_cli_payload +from ..quota.effective_action import EffectiveAction from ..quota.should_run import build_quota_should_run from ..work_items.interaction_contract import build_interaction_contract from ..quota.turn_envelope import quota_action_signature_document @@ -347,7 +348,7 @@ def turn_scenario_source( "goal_id": ACTUAL_DEFAULT_MODEL_BEHAVIOR_FIXTURE_GOAL_ID, "decision": "skip" if human_gate else "run", "should_run": not human_gate, - "effective_action": "operator_gate" if human_gate else "normal_run", + "effective_action": EffectiveAction.OPERATOR_GATE_NOTIFY.value if human_gate else EffectiveAction.NORMAL_RUN.value, "state": "operator_gate" if human_gate else "eligible", "requires_user_action": human_gate, "gate_prompt": ("Approve the bounded public release." if human_gate else None), diff --git a/loopx/control_plane/testing/host_prompt_behavior.py b/loopx/control_plane/testing/host_prompt_behavior.py index b80d8eaae6..b90daad821 100644 --- a/loopx/control_plane/testing/host_prompt_behavior.py +++ b/loopx/control_plane/testing/host_prompt_behavior.py @@ -6,6 +6,7 @@ from pathlib import Path from ...heartbeat_prompt import build_heartbeat_prompt +from ..quota.effective_action import EffectiveAction from .model_tool_behavior import DoubaoExecToolClient @@ -25,7 +26,10 @@ def cases() -> list[dict]: "packet": { "ok": True, "should_run": work, - "effective_action": "autonomous_replan_required" if replan else "run" if work else "wait", + "effective_action": ( + EffectiveAction.AUTONOMOUS_REPLAN_REQUIRED.value if replan + else EffectiveAction.NORMAL_RUN.value if work else EffectiveAction.QUOTA_SKIP.value + ), "execution_obligation": {"must_attempt_work": work}, "heartbeat_recommendation": {"agent_must_attempt": work}, "autonomous_replan_obligation": {"required": replan}, diff --git a/loopx/control_plane/testing/replan_semantic_action_behavior.py b/loopx/control_plane/testing/replan_semantic_action_behavior.py index 9d30666335..0f08ffc89c 100644 --- a/loopx/control_plane/testing/replan_semantic_action_behavior.py +++ b/loopx/control_plane/testing/replan_semantic_action_behavior.py @@ -11,6 +11,7 @@ from typing import Any from ...heartbeat_prompt import build_heartbeat_prompt +from ..quota.effective_action import EffectiveAction from ..quota.turn_envelope import quota_action_signature_document from ..work_items.progress_observation import ( ProgressResultClass, @@ -561,7 +562,7 @@ def _successor_reentry_observation( raise ValueError("successor_reentry_replan_not_closed") if not ( packet.get("decision") == "run" - and packet.get("effective_action") == "normal_run" + and packet.get("effective_action") == EffectiveAction.NORMAL_RUN.value ): raise ValueError("successor_reentry_not_runnable") @@ -622,7 +623,7 @@ def _semantic_reentry_observation( else None ) future_monitor_wait = bool( - packet.get("effective_action") == "monitor_quiet_skip" + packet.get("effective_action") == EffectiveAction.MONITOR_QUIET_SKIP.value and isinstance(frontier, Mapping) and frontier.get("replan_required") is False and isinstance(monitor_lanes, Mapping) diff --git a/loopx/control_plane/todos/decision_scope.py b/loopx/control_plane/todos/decision_scope.py index 2d3b2ef3de..5c49271f5f 100644 --- a/loopx/control_plane/todos/decision_scope.py +++ b/loopx/control_plane/todos/decision_scope.py @@ -1,5 +1,6 @@ """Legacy input codec for the single typed decision-dependency rule owner.""" from __future__ import annotations +from ..quota.effective_action import EffectiveAction from typing import Any @@ -235,7 +236,7 @@ def build_required_decision_scope_repair_hint( "source": "quota.should-run", "trigger": "user_gate_scope_projection_drift", "recommended_mode": "repair_user_gate_scope_projection", - "effective_action": "todo_decision_scope_projection_repair", + "effective_action": EffectiveAction.TODO_DECISION_SCOPE_PROJECTION_REPAIR.value, "blocked_action_scope": "todo_user_gate_scope_projection", "allowed": True, "notify": "DONT_NOTIFY", @@ -257,7 +258,7 @@ def build_required_decision_scope_repair_hint( "source": "quota.should-run", "trigger": "required_decision_scope_projection_drift", "recommended_mode": "repair_required_decision_scope_projection", - "effective_action": "todo_decision_scope_projection_repair", + "effective_action": EffectiveAction.TODO_DECISION_SCOPE_PROJECTION_REPAIR.value, "blocked_action_scope": "todo_decision_scope_projection", "allowed": True, "notify": "DONT_NOTIFY", diff --git a/loopx/control_plane/todos/user_gate.py b/loopx/control_plane/todos/user_gate.py index 41bfb042df..3a52355c6b 100644 --- a/loopx/control_plane/todos/user_gate.py +++ b/loopx/control_plane/todos/user_gate.py @@ -1,4 +1,5 @@ from __future__ import annotations +from ..quota.effective_action import EffectiveAction from typing import Any @@ -159,8 +160,8 @@ def apply_scoped_user_gate_fallback_projection( projected["should_run"] = True if projected.get("decision") == "skip": projected["decision"] = "safe_bypass_user_gate_fallback" - if projected.get("effective_action") in {"skip", "monitor_quiet_skip", None}: - projected["effective_action"] = "scoped_user_gate_fallback" + if projected.get("effective_action") in {EffectiveAction.QUOTA_SKIP.value, EffectiveAction.MONITOR_QUIET_SKIP.value, None}: + projected["effective_action"] = EffectiveAction.SCOPED_USER_GATE_FALLBACK.value raw_execution_obligation = projected.get("execution_obligation") execution_obligation = ( diff --git a/loopx/control_plane/turn_driver/driver.py b/loopx/control_plane/turn_driver/driver.py index acd414495e..ef2180ea4d 100644 --- a/loopx/control_plane/turn_driver/driver.py +++ b/loopx/control_plane/turn_driver/driver.py @@ -1,4 +1,5 @@ from __future__ import annotations +from ..quota.effective_action import EffectiveAction import json from collections.abc import Mapping @@ -96,7 +97,7 @@ def _typed_route(envelope: Mapping[str, Any]) -> LoopXTurnRoute: if should_run: if not delivery_allowed or not must_attempt: return LoopXTurnRoute.BLOCKED - if effective_action == "governed_capability_intent": + if effective_action == EffectiveAction.GOVERNED_CAPABILITY_INTENT.value: intent = _mapping(action.get("capability_intent")) if (intent.get("schema_version") != "pending_capability_intent_projection_v0" or intent.get("goal_id") != envelope.get("goal_id") diff --git a/loopx/control_plane/turn_driver/host_todo_completion.ts b/loopx/control_plane/turn_driver/host_todo_completion.ts index 3e8d0dfa5d..41bdeb7bd9 100644 --- a/loopx/control_plane/turn_driver/host_todo_completion.ts +++ b/loopx/control_plane/turn_driver/host_todo_completion.ts @@ -1,3 +1,4 @@ +import { EffectiveAction } from "../quota/effective_action.generated.ts"; import { createHash } from "node:crypto"; import { @@ -590,7 +591,7 @@ function guardSelection(value: string): GuardSelection { } if ( guard.should_run === false && - guard.effective_action === "terminal_no_followup" + guard.effective_action === EffectiveAction.TERMINAL_NO_FOLLOWUP ) { return { state: "terminal_no_selection", diff --git a/loopx/control_plane/turn_driver/loop_controller.py b/loopx/control_plane/turn_driver/loop_controller.py index b5d196107e..e3af43e19b 100644 --- a/loopx/control_plane/turn_driver/loop_controller.py +++ b/loopx/control_plane/turn_driver/loop_controller.py @@ -18,6 +18,7 @@ """ from __future__ import annotations +from ..quota.effective_action import EffectiveAction from collections.abc import Mapping from enum import Enum @@ -437,7 +438,7 @@ def _completion_disposition( continuation = str(completion.get("continuation") or "") if continuation == "no_followup": if ( - decision.get("effective_action") != "terminal_no_followup" + decision.get("effective_action") != EffectiveAction.TERMINAL_NO_FOLLOWUP.value or decision.get("state") != "terminal_no_followup" ): raise ValueError( @@ -529,7 +530,7 @@ def decide_loop_disposition( ) if turn_receipt is None: - if str(quota_decision.get("effective_action") or "") == "terminal_no_followup": + if str(quota_decision.get("effective_action") or "") == EffectiveAction.TERMINAL_NO_FOLLOWUP.value: if quota_decision.get("state") != "terminal_no_followup": raise ValueError( "terminal no-follow-up requires fresh Goal frontier state" diff --git a/loopx/control_plane/turn_driver/turn_journal.ts b/loopx/control_plane/turn_driver/turn_journal.ts index 807e639882..2ae7fa80c0 100644 --- a/loopx/control_plane/turn_driver/turn_journal.ts +++ b/loopx/control_plane/turn_driver/turn_journal.ts @@ -7,6 +7,7 @@ import { SETTLEMENT_IDENTITY_SCHEMA_VERSION, SETTLEMENT_PLAN_SCHEMA_VERSION, settlementIdentityFromPlan, + type EffectObservation, type EffectTurn, } from "../effect_program.ts"; import { EffectRuntimeRequestError } from "../effect_runtime_errors.ts"; @@ -93,10 +94,14 @@ export interface TurnJournalEffectContext { last_recovery: TurnRecoveryAudit | null; } -export type TurnJournalEffect = EffectTurn< +// Replay has its own verdict. It is not a quota decision and must not manufacture +// a second action vocabulary in the should-run effective_action slot. +export type TurnJournalEffect = Omit; +>, "observation"> & { + observation: Omit, "effective_action">; +}; export const transactionPhases = Object.freeze([...transactionContract.phases]); export const supportedJournalStatuses: ReadonlySet = new Set([ @@ -653,7 +658,6 @@ export function interpretTurnJournalEffect( observation: { decision, should_run: false, - effective_action: replayLegal ? "observe_replay" : "block_replay", recommended_action: replayLegal ? "Retain the terminal Turn journal tombstone." : "Inspect the structured Turn journal violations before replay.", diff --git a/loopx/control_plane/work_items/goal_route_hint.py b/loopx/control_plane/work_items/goal_route_hint.py index fe4a775b73..4080bdd551 100644 --- a/loopx/control_plane/work_items/goal_route_hint.py +++ b/loopx/control_plane/work_items/goal_route_hint.py @@ -3,6 +3,7 @@ from typing import Any from ...state_projection import actions_are_projection_aligned +from ..agents.agent_scope_frontier import read_frontier_action from ..todos.contract import ( TODO_STATUS_OPEN, TODO_TASK_CLASS_ADVANCEMENT, @@ -258,11 +259,7 @@ def build_goal_route_hint( route_decision = "run_current_agent_lane" reason = "quota selected a runnable current-agent lane todo" elif isinstance(agent_scope_frontier, dict): - route_decision = str( - agent_scope_frontier.get("effective_action") - or agent_scope_frontier.get("action") - or "agent_scope_frontier" - ) + route_decision = read_frontier_action(agent_scope_frontier) or "agent_scope_frontier" reason = str(agent_scope_frontier.get("reason") or "agent-scope frontier blocks delivery") elif blocking_handoff_gates or other_agent_actions: route_decision = "wait_or_reassign_other_agent_lane" diff --git a/loopx/control_plane/work_items/interaction_contract.py b/loopx/control_plane/work_items/interaction_contract.py index fccf313982..ef6818ecd6 100644 --- a/loopx/control_plane/work_items/interaction_contract.py +++ b/loopx/control_plane/work_items/interaction_contract.py @@ -1,5 +1,5 @@ from __future__ import annotations - +from ..quota.effective_action import EffectiveAction import shlex import typing from collections.abc import Mapping @@ -454,23 +454,23 @@ def _interaction_mode(payload: dict[str, Any]) -> str: kind = str(execution_obligation.get("kind") or "") effective_action = str(payload.get("effective_action") or "") state = str(payload.get("state") or "") - if effective_action == "governed_capability_intent": + if effective_action == EffectiveAction.GOVERNED_CAPABILITY_INTENT.value: return effective_action - if effective_action == "unsettled_host_turn_recovery": + if effective_action == EffectiveAction.UNSETTLED_HOST_TURN_RECOVERY.value: return effective_action - if effective_action == "agent_monitor_only": + if effective_action == EffectiveAction.AGENT_MONITOR_ONLY.value: return "agent_monitor_only" - if effective_action == "monitor_due": + if effective_action == EffectiveAction.MONITOR_DUE.value: return "monitor_due" - if effective_action == "terminal_no_followup" or state == "terminal_no_followup": + if effective_action == EffectiveAction.TERMINAL_NO_FOLLOWUP.value or state == "terminal_no_followup": return "terminal_no_followup" - if effective_action == "peer_coordination_blocked": + if effective_action == EffectiveAction.PEER_COORDINATION_BLOCKED.value: return effective_action if payload.get("scoped_user_gate_fallback"): return "scoped_user_gate_fallback" if _user_gate_notification_suppressed(payload): return "user_gate_cooldown_wait" - if effective_action == "automation_prompt_upgrade_required": + if effective_action == EffectiveAction.AUTOMATION_PROMPT_UPGRADE_REQUIRED.value: return "automation_prompt_upgrade" if user_channel_action_required(payload): if ( @@ -495,22 +495,22 @@ def _interaction_mode(payload: dict[str, Any]) -> str: return "external_evidence_observation" if kind == AUTONOMOUS_REPLAN_REQUIRED_MODE: return "autonomous_replan" - if effective_action == "coordinate_task_bundle": + if effective_action == EffectiveAction.COORDINATE_TASK_BUNDLE.value: return "task_orchestration" agent_scope_action = _agent_scope_frontier_action(effective_action) if agent_scope_action is not None: return agent_scope_action.value - if effective_action == "monitor_quiet_skip": + if effective_action == EffectiveAction.MONITOR_QUIET_SKIP.value: return "monitor_quiet_skip" - if effective_action == "heartbeat_settled_skip": + if effective_action == EffectiveAction.HEARTBEAT_SETTLED_SKIP.value: return "heartbeat_settled_skip" - if payload.get("recovery_delivery_allowed") or effective_action == "outcome_floor_recovery": + if payload.get("recovery_delivery_allowed") or effective_action == EffectiveAction.OUTCOME_FLOOR_RECOVERY.value: return "outcome_floor_recovery" - if effective_action == "capability_bridge_repair": + if effective_action == EffectiveAction.CAPABILITY_BRIDGE_REPAIR.value: return "capability_bridge_repair" - if effective_action == "agent_workspace_repair": + if effective_action == EffectiveAction.AGENT_WORKSPACE_REPAIR.value: return effective_action - if effective_action == "boundary_projection_repair": + if effective_action == EffectiveAction.BOUNDARY_PROJECTION_REPAIR.value: return "boundary_projection_repair" if payload.get("self_repair_allowed"): return "control_plane_self_repair" diff --git a/loopx/ready_score.py b/loopx/ready_score.py index bcb13fa339..586ff9b996 100644 --- a/loopx/ready_score.py +++ b/loopx/ready_score.py @@ -1,4 +1,5 @@ from __future__ import annotations +from .control_plane.quota.effective_action import EffectiveAction from typing import Any from urllib.parse import quote @@ -265,7 +266,7 @@ def build_ready_score_report( quota_points += 8 elif should_run: quota_points += 5 - if normal_allowed or effective_action == "normal_run": + if normal_allowed or effective_action == EffectiveAction.NORMAL_RUN.value: quota_points += 5 if scheduler_apply_needed is False: quota_points += 4 diff --git a/loopx/semantics/__init__.py b/loopx/semantics/__init__.py index 5011a36f97..949a49faef 100644 --- a/loopx/semantics/__init__.py +++ b/loopx/semantics/__init__.py @@ -1,7 +1,7 @@ """Repository-wide semantic vocabulary registry and inventory. ``vocabulary_v0.json`` is the curated registry (what each cross-module vocabulary -may carry, who defines it, how vocabularies relate). ``inventory_v0.json`` is the -generated map of every closed-set vocabulary carrier under ``loopx/``. Both are -checked, never imported, by product code; see the semantic vocabulary RFC. +may carry, who defines it, how vocabularies relate). The inventory is computed +from tracked sources under ``loopx/``; optional reports are not authority. +Product code imports neither registry nor inventory; see the vocabulary RFC. """ diff --git a/loopx/semantics/inventory_v0.json b/loopx/semantics/inventory_v0.json deleted file mode 100644 index 00ec902542..0000000000 --- a/loopx/semantics/inventory_v0.json +++ /dev/null @@ -1,923 +0,0 @@ -{ - "schema_version": "loopx_semantic_inventory_v0", - "root": "loopx", - "generator": "scripts/generate_semantic_inventory.py", - "advisory": "Structural map only. Consumer counts are printed by the generator's --report and are not committed. Single-module string constants are counted, not listed.", - "python_enums": [ - {"name": "BenchmarkContinuationDecision", "module": "loopx/capabilities/benchmark_toolkit/continuation.py", "values": ["continue", "stop_complete", "stop_progress_regression", "stop_prompt_mismatch", "stop_round_limit", "stop_task_shape_mismatch", "stop_time_budget"]}, - {"name": "NetworkRequestScope", "module": "loopx/capabilities/benchmark_toolkit/integrity.py", "values": ["none", "loopback", "external"]}, - {"name": "RestrictedAccessAdjudicationDecision", "module": "loopx/capabilities/benchmark_toolkit/integrity.py", "values": ["qualified_with_warning", "confirmed_cheating"]}, - {"name": "PublicTrajectoryLifecycleState", "module": "loopx/capabilities/benchmark_toolkit/public_trajectory.py", "values": ["attached", "in_progress", "turn_terminal", "goal_terminal"]}, - {"name": "RunPermissionAction", "module": "loopx/capabilities/benchmark_toolkit/run_permissions.py", "values": ["codex_model_invocation", "local_docker_runner", "local_harbor_runner", "benchmark_dependency_fetch", "compact_result_reduction", "public_result_upload", "leaderboard_submission", "public_benchmark_claim", "production_cloud_action", "credential_sync", "raw_artifact_publication"]}, - {"name": "BenchmarkEventWindowState", "module": "loopx/capabilities/benchmark_toolkit/runtime_continuity.py", "values": ["qualified", "missing", "ambiguous", "outside_launch_window"]}, - {"name": "BenchmarkRuntimeContinuityClassification", "module": "loopx/capabilities/benchmark_toolkit/runtime_continuity.py", "values": ["continuity_input_invalid", "qualified", "generation_mismatch", "runtime_artifact_mismatch", "event_window_missing", "event_window_ambiguous", "event_window_outside_launch"]}, - {"name": "BenchmarkRuntimeContinuityTransition", "module": "loopx/capabilities/benchmark_toolkit/runtime_continuity.py", "values": ["repair_continuity_evidence", "accept_closeout", "route_to_launch_generation", "reject_runtime_artifact", "repair_event_window_evidence"]}, - {"name": "BenchmarkJobReceiptState", "module": "loopx/capabilities/benchmark_toolkit/runtime_observation.py", "values": ["resolved", "missing", "ambiguous"]}, - {"name": "BenchmarkRunnerOwnerState", "module": "loopx/capabilities/benchmark_toolkit/runtime_observation.py", "values": ["alive", "absent_after_grace", "unknown"]}, - {"name": "BenchmarkRuntimeClassification", "module": "loopx/capabilities/benchmark_toolkit/runtime_observation.py", "values": ["not_admitted", "running_qualified", "terminal_pending_reconcile", "runner_invalid_pending_reconcile", "runtime_authority_unresolved", "runner_lost_pending_reconcile", "startup_or_liveness_unresolved"]}, - {"name": "BenchmarkRuntimeTransition", "module": "loopx/capabilities/benchmark_toolkit/runtime_observation.py", "values": ["none", "write_terminal_then_release", "write_runner_invalid_then_release", "repair_runtime_authority", "reobserve_after_startup_grace"]}, - {"name": "PageDensityOrder", "module": "loopx/capabilities/content_ops/layout.py", "values": ["first_page_maximum"]}, - {"name": "PageRole", "module": "loopx/capabilities/content_ops/layout.py", "values": ["cover", "argument", "mechanism", "evidence", "boundary", "closing", "cta"]}, - {"name": "CounterfactualDecision", "module": "loopx/capabilities/explore/counterfactual_runtime.py", "values": ["promoted", "rejected", "observed_only"]}, - {"name": "ItemFailurePolicy", "module": "loopx/capabilities/explore/harness_runtime.py", "values": ["record", "fatal"]}, - {"name": "ReplayChildConcurrency", "module": "loopx/capabilities/explore/replay_runtime.py", "values": ["serial_only", "thread_safe", "process_isolated"]}, - {"name": "ReplayChildFailureStage", "module": "loopx/capabilities/explore/replay_runtime.py", "values": ["intent", "suffix"]}, - {"name": "ReplayFidelity", "module": "loopx/capabilities/explore/replay_runtime.py", "values": ["exact", "semantic_equivalent", "best_effort", "non_replayable"]}, - {"name": "ReplayPointLifecycle", "module": "loopx/capabilities/explore/replay_runtime.py", "values": ["ready", "releasing", "released", "failed"]}, - {"name": "ReplayRiskDisposition", "module": "loopx/capabilities/explore/replay_runtime.py", "values": ["allow", "deny"]}, - {"name": "TraceEventKind", "module": "loopx/capabilities/explore/trace_runtime.py", "values": ["agent_context", "action_intent", "action_outcome", "environment_observation", "validation", "lifecycle"]}, - {"name": "PullRequestReviewPriority", "module": "loopx/capabilities/pr_review_queue/scheduling.py", "values": ["other-developers-first", "owner-first"]}, - {"name": "PullRequestSchedulingLane", "module": "loopx/capabilities/pr_review_queue/scheduling.py", "values": ["authenticated_developer_owned", "community_feedback", "community_aged_backlog", "composite_remaining", "current_head_concluded", "merged", "draft", "closed"]}, - {"name": "ClockSource", "module": "loopx/capabilities/reliability_diagnostics/envelope.py", "values": ["harness_event_time", "observer_wall_clock", "fixture"]}, - {"name": "EnvelopeRejection", "module": "loopx/capabilities/reliability_diagnostics/envelope.py", "values": ["schema_mismatch", "control_field_rejected", "raw_material_field_rejected", "unsupported_field_rejected", "identity_invalid", "sequence_invalid", "clock_invalid", "event_kind_invalid", "summary_invalid", "source_ref_invalid", "public_safety_violation", "observer_internal_failure"]}, - {"name": "ObserverEventKind", "module": "loopx/capabilities/reliability_diagnostics/envelope.py", "values": ["session_started", "turn_started", "turn_ended", "step_started", "step_ended", "user_message", "tool_called", "tool_completed", "agent_status", "agent_pre_step", "agent_error", "session_disposed", "unsupported"]}, - {"name": "DiagnosticSignal", "module": "loopx/capabilities/reliability_diagnostics/projection.py", "values": ["stall_suspected", "repetition_suspected", "unrecovered_error", "event_loss", "integrity_not_valid"]}, - {"name": "DiagnosticStage", "module": "loopx/capabilities/reliability_diagnostics/projection.py", "values": ["unknown", "idle", "running", "tool_running", "errored", "disposed"]}, - {"name": "ReceiptReason", "module": "loopx/capabilities/reliability_diagnostics/receipt.py", "values": ["no_observations", "outbound_endpoint_configured", "observation_entered_worker_context", "observer_failure", "control_field_rejected", "public_safety_violation", "ledger_record_invalid", "observer_stats_missing", "observer_stats_mismatch", "identity_rejected", "observation_entered_scheduler_inputs", "sequence_gap", "sequence_duplicate", "backpressure_drop", "raw_material_rejected", "unsupported_field_rejected", "clock_uncertainty_exceeded"]}, - {"name": "ReceiptStatus", "module": "loopx/capabilities/reliability_diagnostics/receipt.py", "values": ["valid", "degraded", "quarantined", "invalid"]}, - {"name": "EnforcementLevel", "module": "loopx/capabilities/repository_change_window/git_hook.py", "values": ["hook_only", "reference_guard"]}, - {"name": "GitSshService", "module": "loopx/capabilities/repository_change_window/git_hook.py", "values": ["git-receive-pack", "git-upload-pack", "git-upload-archive"]}, - {"name": "ReferenceTransactionPhase", "module": "loopx/capabilities/repository_change_window/git_hook.py", "values": ["preparing", "prepared", "committed", "aborted"]}, - {"name": "AgentLaneFrontierHintDecision", "module": "loopx/control_plane/agents/agent_scope_frontier.py", "values": ["claim_unowned_in_scope", "add_next_advancement", "record_no_followup", "quiet_noop_blocker"]}, - {"name": "AgentScopeFrontierAction", "module": "loopx/control_plane/agents/agent_scope_frontier.py", "values": ["agent_scope_exhausted", "agent_scope_wait", "reassignment_required", "successor_replan_required"]}, - {"name": "AgentRuntimeModel", "module": "loopx/control_plane/agents/runtime_model.py", "values": ["peer_v1"]}, - {"name": "SupervisorDecisionKind", "module": "loopx/control_plane/agents/supervisor.py", "values": ["observe", "inject", "handoff", "discard"]}, - {"name": "SupervisorReceiptOutcome", "module": "loopx/control_plane/agents/supervisor_events.py", "values": ["executed", "rejected", "failed"]}, - {"name": "SupervisorRollbackMode", "module": "loopx/control_plane/agents/supervisor_events.py", "values": ["compensating_action", "not_reversible"]}, - {"name": "DecisionOutcome", "module": "loopx/control_plane/coordination/authority_core.py", "values": ["apply", "no_change", "conflict", "rejected"]}, - {"name": "HandoffMode", "module": "loopx/control_plane/coordination/authority_core.py", "values": ["legacy", "soft_claim", "hard_lease"]}, - {"name": "LeaseAction", "module": "loopx/control_plane/coordination/authority_core.py", "values": ["acquire", "renew", "transfer", "release"]}, - {"name": "LeaseFence", "module": "loopx/control_plane/coordination/authority_core.py", "values": ["not_required", "required", "auto_acquire", "delegated_override"]}, - {"name": "OwnershipGate", "module": "loopx/control_plane/coordination/authority_core.py", "values": ["not_required", "require_holder", "delegated_override"]}, - {"name": "TodoAction", "module": "loopx/control_plane/coordination/authority_core.py", "values": ["claim", "update", "complete", "supersede"]}, - {"name": "ReceiptBoundMonitorPhase", "module": "loopx/control_plane/effect_program.py", "values": ["poll_due", "settlement_pending", "settled"]}, - {"name": "ReceiptBoundReplayPhase", "module": "loopx/control_plane/effect_program.py", "values": ["open", "settlement_pending", "settled"]}, - {"name": "SettlementBindingKind", "module": "loopx/control_plane/effect_program.py", "values": ["todo", "autonomous_replan", "unbound"]}, - {"name": "SettlementFailureKind", "module": "loopx/control_plane/effect_program.py", "values": ["invalid_identity", "receipt_missing", "identity_mismatch", "writeback_missing", "writeback_rejected", "quota_spend_rejected", "terminal_closeout_rejected", "cancelled", "permission_denied", "budget_rejected", "effect_outcome_unknown"]}, - {"name": "SettlementStepKind", "module": "loopx/control_plane/effect_program.py", "values": ["validation", "durable_writeback", "quota_spend", "terminal_closeout"]}, - {"name": "GoalActivationState", "module": "loopx/control_plane/goals/activation.py", "values": ["active", "stopped"]}, - {"name": "GoalActivationAuthorityRouteMode", "module": "loopx/control_plane/goals/activation_service.py", "values": ["source_to_global", "requested_to_global", "orphaned_global_stop_fallback"]}, - {"name": "GoalActivationSourceStatus", "module": "loopx/control_plane/goals/activation_service.py", "values": ["available", "registry_missing", "registry_unreadable", "goal_missing"]}, - {"name": "BotmuxDispatchState", "module": "loopx/control_plane/goals/botmux_runtime.py", "values": ["attempting", "queued", "running", "completed", "failed", "not_found", "unknown", "rejected"]}, - {"name": "OutcomeCheckpointReason", "module": "loopx/control_plane/goals/goal_frontier/outcome_continuity.py", "values": ["final_outcome_claim_missing", "final_outcome_checkpoint_incomplete"]}, - {"name": "GoalFrontierReplanRule", "module": "loopx/control_plane/goals/goal_frontier/replan_rules.py", "values": ["existing_obligation", "blocking_handoff_gate", "ready_deferred_successor", "open_user_todo", "user_action_owns_empty_frontier", "todo_succession_gap", "vision_acceptance_gap", "long_todo_chain", "current_agent_blocker", "monitor_no_change_streak", "not_monitor_only", "no_open_monitor", "advancement_remains", "due_monitor_execution", "future_monitor_wait", "monitor_frontier_exhausted"]}, - {"name": "GoalVisionAdvancementPolicy", "module": "loopx/control_plane/goals/goal_vision_policy.py", "values": ["as_needed", "repeat_until_closed"]}, - {"name": "HostGuardState", "module": "loopx/control_plane/host_adapter_settlement.py", "values": ["selected", "terminal_no_selection", "invalid"]}, - {"name": "QuotaIdentityPrecondition", "module": "loopx/control_plane/quota/error_codes.py", "values": ["public_safe_agent_id", "registered_agent_roster_present", "requested_agent_registered"]}, - {"name": "AutomaticTurnPauseCause", "module": "loopx/control_plane/quota/states.py", "values": ["goal_stopped", "compute_quota_zero"]}, - {"name": "SchedulerDisposition", "module": "loopx/control_plane/scheduler/arbitration.py", "values": ["terminal_stop", "peer_coordination_stop", "agent_monitor_only_wait", "active_work", "agent_scope_wait", "consistency_repair", "human_gate", "monitor_wait", "quiet_wait", "unchanged_wait"]}, - {"name": "ExecutionMode", "module": "loopx/control_plane/scheduler/execution_context.py", "values": ["interactive", "isolated_headless", "hosted_automation"]}, - {"name": "GoalRuntimeContinuationDisposition", "module": "loopx/control_plane/scheduler/execution_context.py", "values": ["continue_now", "defer", "complete"]}, - {"name": "HostSurface", "module": "loopx/control_plane/scheduler/execution_context.py", "values": ["ark_managed_agent", "codex_app", "codex_app_ssh", "codex_cli", "trae_app", "generic_cli", "claude_code", "kunluncode", "local_scheduler"]}, - {"name": "SchedulerOwner", "module": "loopx/control_plane/scheduler/execution_context.py", "values": ["host_automation", "agent_cli_loop", "goal_runtime", "outer_controller", "none"]}, - {"name": "SchedulerRuntimeProfile", "module": "loopx/control_plane/scheduler/execution_context.py", "values": ["ark_managed_agent_goal", "codex_app_heartbeat", "codex_app_ssh_goal", "codex_cli", "trae_app", "claude_code", "kunluncode", "generic_cli", "outer_controller"]}, - {"name": "MonitorWaitPhase", "module": "loopx/control_plane/scheduler/monitor_wait.py", "values": ["expired", "active_window", "near_window", "far_window", "cadence_only"]}, - {"name": "SchedulerCadenceTransition", "module": "loopx/control_plane/scheduler/state_transition_rules.py", "values": ["initial", "identity_reset", "retry_unacknowledged_failure", "hold_active_initial", "advance_after_interval", "hold_until_interval"]}, - {"name": "SchedulerHostTransition", "module": "loopx/control_plane/scheduler/state_transition_rules.py", "values": ["apply_required", "host_match_ack_required", "recorded_failure_suppressed", "settled"]}, - {"name": "TodoCompletionContinuation", "module": "loopx/control_plane/todos/completion_state.py", "values": ["active_goal", "successor", "no_followup"]}, - {"name": "TodoCompletionRecovery", "module": "loopx/control_plane/todos/completion_state.py", "values": ["same_turn_terminal_closeout", "lifecycle_reentry_terminal_closeout"]}, - {"name": "TodoContinuationPolicy", "module": "loopx/control_plane/todos/contract.py", "values": ["independent_handoff", "same_agent_non_delivery"]}, - {"name": "HandoffGateState", "module": "loopx/control_plane/todos/handoff_gate.py", "values": ["blocking", "cleared_without_successor", "cleared_with_successor", "cleared_no_followup", "superseded", "deferred"]}, - {"name": "ProjectionDeliveryStatus", "module": "loopx/control_plane/todos/provider_projection.py", "values": ["pending", "delivered", "current", "not_required"]}, - {"name": "LoopXTurnRoute", "module": "loopx/control_plane/turn_driver/driver.py", "values": ["ready_for_host", "capability_action_required", "repair_required", "replan_required", "user_action_required", "wait", "blocked", "contract_error"]}, - {"name": "LoopDisposition", "module": "loopx/control_plane/turn_driver/loop_controller.py", "values": ["run_now", "capability_action_required", "wait", "stop", "user_action_required", "repair", "replan", "terminal"]}, - {"name": "LoopXTurnResultKind", "module": "loopx/control_plane/turn_driver/transaction.py", "values": ["validated_progress", "validated_completion", "repair_required", "replan_required", "user_action_required", "wait", "iteration_failed", "host_failure", "validation_failed", "writeback_failed", "quota_spend_failed", "terminal_closeout_failed"]}, - {"name": "DeliveryBatchScale", "module": "loopx/control_plane/work_items/delivery_batch_scale.py", "values": ["test_only", "single_surface", "multi_surface", "implementation"]}, - {"name": "DeliveryOutcome", "module": "loopx/control_plane/work_items/delivery_outcome.py", "values": ["surface_only", "outcome_gap", "outcome_progress", "primary_goal_outcome"]}, - {"name": "DeliveryTurnKind", "module": "loopx/control_plane/work_items/delivery_outcome.py", "values": ["contract_only_preparation", "compact_evidence", "blocker_writeback", "product_path_execution", "outcome_gap", "unknown"]}, - {"name": "GovernedTransitionSettlementPhase", "module": "loopx/control_plane/work_items/governed_transition_proposal.py", "values": ["pre_settlement", "post_settlement"]}, - {"name": "ProgressResultClass", "module": "loopx/control_plane/work_items/progress_result.py", "values": ["advanced", "unchanged", "blocked", "exploration_exhausted", "no_followup"]}, - {"name": "GitRevisionRelation", "module": "loopx/doctor_git.py", "values": ["same", "installed_ahead", "installed_behind", "diverged", "unknown"]}, - {"name": "ExternalCapturePolicy", "module": "loopx/extensions/external_connector_runtime.py", "values": ["addressed_only", "configured_source_all", "incremental"]}, - {"name": "ExternalConnectorCapability", "module": "loopx/extensions/external_connector_runtime.py", "values": ["realtime_receive", "history_catch_up", "response_write", "response_readback", "acknowledge"]}, - {"name": "ExternalConnectorLifecycle", "module": "loopx/extensions/external_connector_runtime.py", "values": ["connected", "listening", "stale", "disconnected"]}, - {"name": "ExternalEffectKind", "module": "loopx/extensions/external_connector_runtime.py", "values": ["working_session_turn", "todo_update", "authority_update", "design_update", "no_follow_up"]}, - {"name": "ExternalIngressPolicy", "module": "loopx/extensions/external_connector_runtime.py", "values": ["live_steering", "session_queue", "async_inbox"]}, - {"name": "ExternalResponsePolicy", "module": "loopx/extensions/external_connector_runtime.py", "values": ["no_response", "source_thread", "topic_reply", "configured_mirror"]}, - {"name": "ExternalSourceKind", "module": "loopx/extensions/external_connector_runtime.py", "values": ["group_message", "document_comment"]}, - {"name": "LarkTopicEventDecisionReason", "module": "loopx/extensions/lark/goal_channel_contracts.py", "values": ["matched", "invalid_event", "binding_unavailable", "chat_mismatch", "topic_mismatch", "route_ambiguous", "self_message", "invalid_routing_state", "not_addressed"]}, - {"name": "BotChatMembershipResult", "module": "loopx/extensions/lark/goal_channel_transport.py", "values": ["already_verified", "already_unverified", "added_verified", "add_failed", "added_unverified"]}, - {"name": "CaptureScope", "module": "loopx/extensions/lark/goal_topic_routing.py", "values": ["addressed_only", "configured_chat_all"]}, - {"name": "IngressMode", "module": "loopx/extensions/lark/goal_topic_routing.py", "values": ["live_steering", "session_queue", "direct_session", "async_inbox"]}, - {"name": "ReplyMode", "module": "loopx/extensions/lark/goal_topic_routing.py", "values": ["topic_reply"]}, - {"name": "BotIdentityVerification", "module": "loopx/extensions/lark/inbox_reply.py", "values": ["verified", "retryable_verify_failed", "rejected"]}, - {"name": "ManagerAuthorityMode", "module": "loopx/extensions/lark/manager_routing.py", "values": ["context_only", "turn_authorized"]}, - {"name": "LockAcquisitionPolicy", "module": "loopx/file_lock.py", "values": ["mutation", "monitor", "single_flight"]}, - {"name": "SourceQuality", "module": "loopx/goal_portfolio.py", "values": ["verified", "stale", "conflicting", "unreadable", "partial", "omitted"]}, - {"name": "UpdateAction", "module": "loopx/self_update.py", "values": ["check", "plan", "apply"]}, - {"name": "KeyState", "module": "loopx/session_runtime.py", "values": ["compact", "raw_material", "unclassified"]}, - {"name": "RawMaterialCategory", "module": "loopx/session_runtime.py", "values": ["credential", "transcript", "log", "local_path", "raw_output"]} - ], - "python_closed_sets": [ - {"name": "AGY_ACCEPTED_INPUTS", "module": "loopx/agy_goal_mode/__init__.py", "container": "tuple", "values": ["agy", "antigravity", "antigravity-cli", "antigravity_cli", "antigravity cli", "google antigravity"]}, - {"name": "AGY_NATIVE_WAKE_FACTS", "module": "loopx/agy_goal_mode/__init__.py", "container": "tuple", "values": ["native `schedule` tool: DurationSeconds + Prompt wake message, recurring wakes via MaxIterations, one-shot early-termination conditions", "background tasks (`manage_task`) and async subagents (`invoke_subagent`/`send_message`) wake a live session without an external driver", "`hooks.json` (user or plugin) runs PostToolUse/Stop/PostInvocation automation on tool events"]}, - {"name": "AGY_NATIVE_WAKE_TOOLS", "module": "loopx/agy_goal_mode/__init__.py", "container": "tuple", "values": ["schedule", "manage_task", "invoke_subagent", "send_message", "manage_inbox"]}, - {"name": "AUTHORITY_REGISTRY_CANONICAL_FIELDS", "module": "loopx/authority.py", "container": "tuple", "values": ["default_entry_docs", "topic_authority", "project_materials", "deprecated_sources"]}, - {"name": "AUTHORITY_REGISTRY_SUMMARY_FIELDS", "module": "loopx/authority.py", "container": "tuple", "values": ["declared", "required", "path", "path_exists", "read_status", "default_entry_count", "default_entries_checked", "default_entries_present", "topic_authority_count", "project_material_count", "project_material_repository_count", "project_material_owner_review_required_count", "project_material_stale_count", "project_material_current_authority_count", "deprecated_source_count", "conflict_risk"]}, - {"name": "AUTHORITY_SOURCE_BOUNDARIES", "module": "loopx/authority.py", "container": "set", "values": ["public", "local_private", "private_redacted"]}, - {"name": "START_GOAL_HOST_SURFACES", "module": "loopx/bootstrap_command_pack.py", "container": "tuple", "values": ["codex-app", "trae_app", "codex-app-ssh", "codex-ide-plugin", "codex-cli-tui", "claude-code", "opencode", "opencode2", "traex-cli", "pi", "gemini-cli", "cursor-agent", "zcode", "agy", "kiro-cli", "deepseek-harness", "deepseek-harness-native", "ark-managed-agent", "shell", "other-agent"]}, - {"name": "ACTIVE_BOUNDARY_AUTHORITY_STATUSES", "module": "loopx/boundary_authority.py", "container": "set", "values": ["active", "approved"]}, - {"name": "BOUNDARY_AUTHORITY_DECISIONS", "module": "loopx/boundary_authority.py", "container": "set", "values": ["approve", "reject", "defer"]}, - {"name": "CONTROL_PLANE_FORBIDDEN_DEPENDENCY_PREFIXES", "module": "loopx/canary/maintainability_ratchet.py", "container": "tuple", "values": ["loopx.capabilities", "loopx.cli", "loopx.cli_commands", "loopx.presentation"]}, - {"name": "BENCHMARK_SENSITIVE_TOKENS", "module": "loopx/canary/premerge.py", "container": "tuple", "values": ["benchmark/", "loopx/capabilities/benchmark_toolkit/", "deprecate/benchmark-legacy/", "loopx/worker_bridge.py", "examples/benchmark"]}, - {"name": "CANARY_TOKENS", "module": "loopx/canary/premerge.py", "container": "tuple", "values": ["loopx/canary/", "loopx/cli_commands/canary.py", "examples/canary/", "tests/test_smoke_suite.py"]}, - {"name": "CHANGE_QUALITY_TOKENS", "module": "loopx/canary/premerge.py", "container": "tuple", "values": ["loopx/capabilities/change_quality/", "skills/loopx-change-quality/", "tests/capabilities/test_change_quality.py", "examples/change-quality-qualification-smoke.py"]}, - {"name": "CONTROL_PLANE_TOKENS", "module": "loopx/canary/premerge.py", "container": "tuple", "values": ["loopx/control_plane/", "loopx/quota.py", "loopx/status.py", "loopx/todos.py", "loopx/state_refresh.py", "loopx/review_packet.py", "loopx/heartbeat_prompt.py", "loopx/cli_commands/quota", "loopx/cli_commands/status", "loopx/cli_commands/todo", "loopx/cli_commands/refresh"]}, - {"name": "DOC_CONTENT_TOKENS", "module": "loopx/canary/premerge.py", "container": "tuple", "values": ["docs/", "README", "AGENTS.md", "docs/development/contributor-tasks.md", "examples/project/", "loopx/capabilities/content_ops/"]}, - {"name": "EXTENSION_RUNTIME_TOKENS", "module": "loopx/canary/premerge.py", "container": "tuple", "values": ["loopx/extensions/", "loopx/cli_commands/extension.py", "examples/capability-extension", "examples/openviking-extension"]}, - {"name": "INSTALL_RELEASE_TOKENS", "module": "loopx/canary/premerge.py", "container": "tuple", "values": ["scripts/install", "scripts/loopx", "loopx/promotion_gate.py", "examples/release/", "examples/public_entry/"]}, - {"name": "LARK_KANBAN_TOKENS", "module": "loopx/canary/premerge.py", "container": "tuple", "values": ["loopx/lark_kanban.py", "loopx/cli_commands/lark_kanban.py", "examples/lark-kanban"]}, - {"name": "PREMERGE_TIERS", "module": "loopx/canary/premerge.py", "container": "set", "values": ["quick", "standard", "deep"]}, - {"name": "PUBLIC_BOUNDARY_TOKENS", "module": "loopx/canary/premerge.py", "container": "tuple", "values": ["docs/", "examples/", "README", "AGENTS.md", ".github/", "package-lock.json"]}, - {"name": "DETERMINISTIC_MINIMUM_LAYERS", "module": "loopx/canary/quality_surface_catalog.py", "container": "tuple", "values": ["unit_contract", "durable_smoke", "catalog_canary"]}, - {"name": "QUALITY_LAYER_IDS", "module": "loopx/canary/quality_surface_catalog.py", "container": "tuple", "values": ["unit_contract", "durable_smoke", "catalog_canary", "host_upgrade", "model_behavior", "release_gate"]}, - {"name": "EXPLICIT_GROUPED_SMOKES", "module": "loopx/canary/runner.py", "container": "set", "values": ["canary-promotion-readiness-smoke.py", "dashboard-demo-readiness-smoke.py"]}, - {"name": "PYTHON_BINARIES", "module": "loopx/canary/runner.py", "container": "set", "values": ["python", "python3"]}, - {"name": "SHELL_TOKENS", "module": "loopx/canary/runner.py", "container": "set", "values": ["&&", "||", ";", "|", ">", "<", ">>", "2>", "2>>"]}, - {"name": "SMOKE_SUITE_CHOICES", "module": "loopx/canary/runner.py", "container": "set", "values": ["default-public", "full-public", "catalog-plan"]}, - {"name": "CADENCE_IDS", "module": "loopx/canary/smoke_health.py", "container": "tuple", "values": ["pr_fast", "catalog_canary", "daily_full_public", "release_gate"]}, - {"name": "NON_BENCHMARK_SMOKE_EXCLUDE_MODULES", "module": "loopx/canary/smoke_profiles.py", "container": "list", "values": ["agentissue-bench", "agents-last-exam", "benchmark", "skillsbench", "swe-marathon", "terminal-bench"]}, - {"name": "BENCHMARK_CANDIDATE_SOURCE_ACTIVE_STATE_MARKERS", "module": "loopx/capabilities/benchmark_toolkit/artifacts.py", "container": "tuple", "values": ["/active_goal_state.md", ".codex/goals/", ".local/goals/"]}, - {"name": "BENCHMARK_CANDIDATE_SOURCE_PRIVATE_RUN_MARKERS", "module": "loopx/capabilities/benchmark_toolkit/artifacts.py", "container": "tuple", "values": [".local/private-benchmark-jobs", "/private-benchmark-jobs/"]}, - {"name": "BENCHMARK_CANDIDATE_SOURCE_PUBLIC_DOC_PREFIXES", "module": "loopx/capabilities/benchmark_toolkit/artifacts.py", "container": "tuple", "values": ["benchmark/", "docs/", "examples/", "goals/", "regression/"]}, - {"name": "BENCHMARK_CANDIDATE_SOURCE_RAW_MARKERS", "module": "loopx/capabilities/benchmark_toolkit/artifacts.py", "container": "tuple", "values": ["/agent/", "/origin_log", "/output/", "/outputs/", "/screenshots/", "/tasks/", "codex.txt", "trajectory.json", "instruction.md", "task.md", "lock.json", "config.json", "result.json"]}, - {"name": "BENCHMARK_PRIVATE_MANIFEST_SUFFIXES", "module": "loopx/capabilities/benchmark_toolkit/artifacts.py", "container": "tuple", "values": [".local.json", ".private.json"]}, - {"name": "BENCHMARK_PUBLIC_ARTIFACT_FILENAMES", "module": "loopx/capabilities/benchmark_toolkit/artifacts.py", "container": "tuple", "values": ["paired_comparison.compact.json", "launch_status.public.json", "launch_summaries.public.json", "loopx-active-user-observation.json"]}, - {"name": "BENCHMARK_PUBLIC_ARTIFACT_SUFFIXES", "module": "loopx/capabilities/benchmark_toolkit/artifacts.py", "container": "tuple", "values": [".compact.json", ".public.json"]}, - {"name": "BENCHMARK_RAW_PRIVATE_PATH_MARKERS", "module": "loopx/capabilities/benchmark_toolkit/artifacts.py", "container": "tuple", "values": ["/agent/trajectory.json", "/sessions/", "/logs/", "/raw/", "trajectory.json", "origin_log", "instruction.md", "task.md", "/screenshots/", "screenshot"]}, - {"name": "BENCHMARK_FOUR_ARM_RUNNER_OBLIGATIONS", "module": "loopx/capabilities/benchmark_toolkit/four_arm_contract.py", "container": "tuple", "values": ["keep_loopx_startup_out_of_band", "match_runtime_task_goal_hash_to_selected_arm", "pin_all_non_factor_inputs", "register_each_run_on_experiment_board"]}, - {"name": "INTEGRITY_EVIDENCE_CATEGORIES", "module": "loopx/capabilities/benchmark_toolkit/integrity.py", "container": "tuple", "values": ["restricted_answer_source_request", "restricted_task_source_request", "restricted_test_source_request", "verifier_source_request", "other_trial_request", "controller_private_state_request", "host_escape_probe", "credential_probe", "credential_value_observed", "loopback_network_request", "external_network_request"]}, - {"name": "NETWORK_ACCESS_MODES", "module": "loopx/capabilities/benchmark_toolkit/integrity.py", "container": "tuple", "values": ["denied", "loopback_only", "permitted_solving"]}, - {"name": "REQUIRED_RUNTIME_ATTESTATIONS", "module": "loopx/capabilities/benchmark_toolkit/integrity.py", "container": "tuple", "values": ["agent_phase_isolated", "evaluator_sources_denied", "other_trials_denied", "controller_state_denied", "host_escape_denied", "shell_network_denied", "provider_credential_shell_excluded", "case_local_control_state", "canonical_control_state_root", "independent_verifier", "verifier_started_after_agent", "official_feedback_blinded"]}, - {"name": "VALIDATION_CATEGORIES", "module": "loopx/capabilities/change_quality/oracles.py", "container": "tuple", "values": ["format", "lint", "typecheck", "test"]}, - {"name": "EVIDENCE_REF_KINDS", "module": "loopx/capabilities/change_quality/result.py", "container": "frozenset", "values": ["path", "instruction", "validator"]}, - {"name": "REUSE_OUTCOMES", "module": "loopx/capabilities/change_quality/result.py", "container": "frozenset", "values": ["reused", "retained", "deferred", "not_applicable"]}, - {"name": "RISK_SEVERITIES", "module": "loopx/capabilities/change_quality/result.py", "container": "frozenset", "values": ["blocker", "warning", "advisory"]}, - {"name": "SIMPLIFICATION_OUTCOMES", "module": "loopx/capabilities/change_quality/result.py", "container": "frozenset", "values": ["fixed", "retained", "deferred", "not_applicable"]}, - {"name": "SIMPLIFY_PRIMARY_LENS_IDS", "module": "loopx/capabilities/change_quality/result.py", "container": "tuple", "values": ["reuse", "quality_simplification"]}, - {"name": "VALIDATION_STATUSES", "module": "loopx/capabilities/change_quality/result.py", "container": "frozenset", "values": ["passed", "failed", "skipped"]}, - {"name": "CHANGE_QUALITY_SHADOW_CASE_KINDS", "module": "loopx/capabilities/change_quality/shadow.py", "container": "tuple", "values": ["real_pr_clean", "real_pr_adversarial", "synthetic_simplification"]}, - {"name": "CHANGE_QUALITY_SHADOW_CONTRACT_VERSIONS", "module": "loopx/capabilities/change_quality/shadow.py", "container": "tuple", "values": ["v0", "v1"]}, - {"name": "CHANGE_QUALITY_SHADOW_FINDING_CLASSES", "module": "loopx/capabilities/change_quality/shadow.py", "container": "tuple", "values": ["correctness", "security_privacy", "api_contract", "validation", "complexity", "efficiency", "error_supervision", "documentation", "release_compatibility"]}, - {"name": "CHANGE_QUALITY_SHADOW_SAFE_FIX_ACTIONS", "module": "loopx/capabilities/change_quality/shadow.py", "container": "tuple", "values": ["none", "bounded", "manual"]}, - {"name": "CHANGE_QUALITY_SHADOW_SIMPLIFICATION_OUTCOMES", "module": "loopx/capabilities/change_quality/shadow.py", "container": "tuple", "values": ["retain", "simplify", "hold"]}, - {"name": "ALLOWED_EFFECT_KINDS", "module": "loopx/capabilities/content_ops/item_lifecycle.py", "container": "set", "values": ["profile_update", "publish", "reply", "repost"]}, - {"name": "ALLOWED_ITEM_KINDS", "module": "loopx/capabilities/content_ops/item_lifecycle.py", "container": "set", "values": ["article", "post", "profile_update", "reply", "repost"]}, - {"name": "ALLOWED_ITEM_STATES", "module": "loopx/capabilities/content_ops/item_lifecycle.py", "container": "set", "values": ["captured", "draft", "review_ready", "approved", "delivery_ready", "published", "readback_verified", "skipped", "superseded"]}, - {"name": "TERMINAL_STATES", "module": "loopx/capabilities/content_ops/item_lifecycle.py", "container": "set", "values": ["readback_verified", "skipped", "superseded"]}, - {"name": "ALLOWED_ANGLE_DECISIONS", "module": "loopx/capabilities/content_ops/surface.py", "container": "set", "values": ["draft", "reject", "hold", "needs_review"]}, - {"name": "ALLOWED_CONNECTOR_ACCESS_MODES", "module": "loopx/capabilities/content_ops/surface.py", "container": "set", "values": ["public_metadata_only", "private_metadata_only", "synthetic_fixture_only"]}, - {"name": "ALLOWED_CONNECTOR_TRIAL_STATES", "module": "loopx/capabilities/content_ops/surface.py", "container": "set", "values": ["candidate", "metadata_packet_collected", "ready_for_metadata_trial", "needs_owner_gate", "blocked"]}, - {"name": "ALLOWED_DRAFT_STATES", "module": "loopx/capabilities/content_ops/surface.py", "container": "set", "values": ["outline", "draft", "rewrite", "blocked", "ready_for_review"]}, - {"name": "ALLOWED_EXPLORATION_EVIDENCE_QUALITIES", "module": "loopx/capabilities/content_ops/surface.py", "container": "set", "values": ["primary_source_metadata", "metadata_only", "not_evidence_until_approved", "compact_result_metadata"]}, - {"name": "ALLOWED_EXPLORATION_READ_STATUSES", "module": "loopx/capabilities/content_ops/surface.py", "container": "set", "values": ["metadata_ready", "not_read", "blocked_until_owner_gate", "compact_result_ready"]}, - {"name": "ALLOWED_FEEDBACK_EFFECTS", "module": "loopx/capabilities/content_ops/surface.py", "container": "set", "values": ["preference_hint", "source_boundary_correction", "rewrite_todo", "publish_decision"]}, - {"name": "ALLOWED_FRESHNESS", "module": "loopx/capabilities/content_ops/surface.py", "container": "set", "values": ["fresh", "stale", "unknown"]}, - {"name": "ALLOWED_PUBLISH_GATE_STATUSES", "module": "loopx/capabilities/content_ops/surface.py", "container": "set", "values": ["blocked_until_user_approval", "approved", "denied", "needs_revision"]}, - {"name": "ALLOWED_SOURCE_STATUSES", "module": "loopx/capabilities/content_ops/surface.py", "container": "set", "values": ["public", "private_needs_review", "synthetic_public_safe", "unpublished", "forbidden_for_public_surface"]}, - {"name": "ALLOWED_USE_POLICIES", "module": "loopx/capabilities/content_ops/surface.py", "container": "set", "values": ["summarize_and_transform", "metadata_only", "do_not_quote", "forbidden"]}, - {"name": "RAW_MATERIAL_KEY_HINTS", "module": "loopx/capabilities/content_ops/surface.py", "container": "tuple", "values": ["body", "chat", "credential", "dm", "local_path", "log", "message", "raw", "secret", "token", "transcript"]}, - {"name": "DECISION_OUTCOME_VERIFICATION_STATUSES", "module": "loopx/capabilities/decision_context/packets.py", "container": "set", "values": ["pending", "verified", "refuted", "inconclusive"]}, - {"name": "DECISION_REVIEW_DISPOSITIONS", "module": "loopx/capabilities/decision_context/packets.py", "container": "set", "values": ["approve", "reject", "defer", "no_change"]}, - {"name": "SHARED_DEPENDENCY_CAPABILITY_PREFIXES", "module": "loopx/capabilities/explore/todo_branch_plan.py", "container": "tuple", "values": ["shared_artifact:", "shared_implementation:"]}, - {"name": "RELEVANT_EDGE_TYPES", "module": "loopx/capabilities/explore/todo_evidence.py", "container": "set", "values": ["supports", "refutes"]}, - {"name": "BRANCH_FILL_POLICIES", "module": "loopx/capabilities/explore/worker_branch_plan.py", "container": "set", "values": ["bundle-by-affinity", "value-first", "confident-prefix"]}, - {"name": "COMMON_TOPIC_TOKENS", "module": "loopx/capabilities/explore/worker_branch_plan.py", "container": "set", "values": ["add", "after", "agent", "against", "and", "audit", "before", "between", "branch", "build", "check", "codex", "continue", "current", "deliver", "existing", "explore", "fix", "from", "goal", "inspect", "into", "latest", "loopx", "new", "next", "one", "p0", "p1", "p2", "recent", "review", "run", "test", "the", "then", "this", "through", "todo", "update", "validate", "when", "while", "with", "without"]}, - {"name": "PROMOTION_DECISIONS", "module": "loopx/capabilities/issue_fix/discovered_issue_promotion.py", "container": "set", "values": ["reuse_existing", "no_equivalent_found"]}, - {"name": "COMMENT_VALUES", "module": "loopx/capabilities/issue_fix/feasibility.py", "container": "set", "values": ["none", "clarification", "diagnosis"]}, - {"name": "REPRODUCTION_STATES", "module": "loopx/capabilities/issue_fix/feasibility.py", "container": "set", "values": ["confirmed", "planned", "missing", "blocked"]}, - {"name": "RESOLUTION_ROUTES", "module": "loopx/capabilities/issue_fix/feasibility.py", "container": "set", "values": ["fix_pr", "comment_only", "triage_only"]}, - {"name": "SCOPE_CLASSES", "module": "loopx/capabilities/issue_fix/feasibility.py", "container": "set", "values": ["bounded", "uncertain", "oversized"]}, - {"name": "ALLOWED_GITHUB_REF_TYPES", "module": "loopx/capabilities/issue_fix/github_public.py", "container": "set", "values": ["issue", "pull", "discussion"]}, - {"name": "GITHUB_COMMENT_BODY_KEYS", "module": "loopx/capabilities/issue_fix/github_public.py", "container": "set", "values": ["body", "body_text", "comment_body", "comment_bodies", "raw", "response_payload", "timeline"]}, - {"name": "MAINTAINER_ASSOCIATIONS", "module": "loopx/capabilities/issue_fix/github_public.py", "container": "set", "values": ["COLLABORATOR", "MEMBER", "OWNER"]}, - {"name": "ALLOWED_ISSUE_FIX_ROUTE_STATUSES", "module": "loopx/capabilities/issue_fix/intake_surface.py", "container": "set", "values": ["candidate", "blocked_until_gate", "selected"]}, - {"name": "ALLOWED_ISSUE_FIX_INTAKE_STATES", "module": "loopx/capabilities/issue_fix/metadata_preview.py", "container": "set", "values": ["open", "closed", "unknown"]}, - {"name": "GITHUB_BODY_OR_COMMENT_KEYS", "module": "loopx/capabilities/issue_fix/metadata_preview.py", "container": "set", "values": ["body", "body_text", "comments", "comment_bodies", "timeline", "events", "raw", "response_payload"]}, - {"name": "BRANCH_REPLAN_MERGE_STATES", "module": "loopx/capabilities/issue_fix/outcome_projection.py", "container": "set", "values": ["BEHIND", "DIRTY"]}, - {"name": "DELIVERY_OUTCOME_STATUSES", "module": "loopx/capabilities/issue_fix/outcome_projection.py", "container": "set", "values": ["in_progress", "completed", "blocked"]}, - {"name": "DELIVERY_VALIDATION_STATUSES", "module": "loopx/capabilities/issue_fix/outcome_projection.py", "container": "set", "values": ["passed", "failed", "partial", "not_run"]}, - {"name": "TERMINAL_PR_STATES", "module": "loopx/capabilities/issue_fix/pr_gate_reconcile.py", "container": "set", "values": ["MERGED", "CLOSED"]}, - {"name": "BRANCH_REPLAN_MERGE_STATES", "module": "loopx/capabilities/issue_fix/pr_lifecycle.py", "container": "set", "values": ["BEHIND", "DIRTY"]}, - {"name": "FAILING_CHECK_STATES", "module": "loopx/capabilities/issue_fix/pr_lifecycle.py", "container": "set", "values": ["ACTION_REQUIRED", "CANCELLED", "ERROR", "FAILURE", "FAILED", "STARTUP_FAILURE", "TIMED_OUT"]}, - {"name": "MAINTAINER_CORRECTION_INPUT_FIELDS", "module": "loopx/capabilities/issue_fix/pr_lifecycle.py", "container": "set", "values": ["schema_version", "correction_kind", "source_kind", "source_ref", "summary", "verification_plan", "pr_update_path", "user_question", "missing_authority_scopes"]}, - {"name": "MAINTAINER_CORRECTION_KINDS", "module": "loopx/capabilities/issue_fix/pr_lifecycle.py", "container": "set", "values": ["actionable_patch", "semantic_ambiguity", "missing_authority", "unchanged"]}, - {"name": "MAINTAINER_CORRECTION_SOURCE_KINDS", "module": "loopx/capabilities/issue_fix/pr_lifecycle.py", "container": "set", "values": ["review", "maintainer_comment"]}, - {"name": "PASSING_CHECK_STATES", "module": "loopx/capabilities/issue_fix/pr_lifecycle.py", "container": "set", "values": ["NEUTRAL", "SKIPPED", "SUCCESS"]}, - {"name": "PENDING_CHECK_STATES", "module": "loopx/capabilities/issue_fix/pr_lifecycle.py", "container": "set", "values": ["EXPECTED", "IN_PROGRESS", "PENDING", "QUEUED", "REQUESTED", "WAITING"]}, - {"name": "TERMINAL_PR_STATES", "module": "loopx/capabilities/issue_fix/pr_lifecycle.py", "container": "set", "values": ["MERGED", "CLOSED"]}, - {"name": "WRITE_SCOPES", "module": "loopx/capabilities/issue_fix/pr_lifecycle.py", "container": "set", "values": ["write", "publish", "external_review_request"]}, - {"name": "CONSULTATION_STATES", "module": "loopx/capabilities/issue_fix/repository_context.py", "container": "set", "values": ["not_applicable", "available", "queried", "unavailable"]}, - {"name": "FRESHNESS_STATES", "module": "loopx/capabilities/issue_fix/repository_context.py", "container": "set", "values": ["current", "stale", "unknown"]}, - {"name": "REQUIRED_FIX_ASPECTS", "module": "loopx/capabilities/issue_fix/repository_context.py", "container": "tuple", "values": ["change_scope", "reproduction", "validation"]}, - {"name": "SOURCE_KINDS", "module": "loopx/capabilities/issue_fix/repository_context.py", "container": "set", "values": ["repository_policy", "architecture_doc", "maintainer_map", "test_surface", "source_code", "prior_fix", "memory_retrieval", "external_expert", "knowledge_bundle"]}, - {"name": "SUPPORT_ASPECTS", "module": "loopx/capabilities/issue_fix/repository_context.py", "container": "set", "values": ["architecture", "ownership", "change_scope", "reproduction", "validation"]}, - {"name": "TRUST_LEVELS", "module": "loopx/capabilities/issue_fix/repository_context.py", "container": "set", "values": ["authoritative", "verified", "advisory"]}, - {"name": "DECISION_INFLUENCE_ASPECTS", "module": "loopx/capabilities/issue_fix/repository_memory.py", "container": "set", "values": ["reproduction", "change_scope", "patch", "validation"]}, - {"name": "PROVIDER_STATUSES", "module": "loopx/capabilities/issue_fix/repository_memory.py", "container": "set", "values": ["completed", "unavailable", "disabled"]}, - {"name": "SUPPORT_ASPECTS", "module": "loopx/capabilities/issue_fix/repository_memory.py", "container": "set", "values": ["architecture", "ownership", "change_scope", "reproduction", "validation"]}, - {"name": "VERIFICATION_STATUSES", "module": "loopx/capabilities/issue_fix/repository_memory.py", "container": "set", "values": ["confirmed", "refuted", "unverified"]}, - {"name": "REVIEWER_COMMANDS", "module": "loopx/capabilities/issue_fix/reviewer_cli.py", "container": "frozenset", "values": ["reviewer-plan", "reviewer-request", "reviewer-notification-drain", "reviewer-feedback-inbox"]}, - {"name": "CODEOWNERS_LOCATIONS", "module": "loopx/capabilities/issue_fix/reviewer_recommendation.py", "container": "tuple", "values": [".github/CODEOWNERS", "CODEOWNERS", "docs/CODEOWNERS"]}, - {"name": "REVIEWER_SOURCE_FIELDS", "module": "loopx/capabilities/issue_fix/reviewer_recommendation.py", "container": "set", "values": ["source_id", "source_kind", "reference", "trust", "freshness", "observed_at", "routes"]}, - {"name": "REVIEWER_SOURCE_FRESHNESS_STATES", "module": "loopx/capabilities/issue_fix/reviewer_recommendation.py", "container": "set", "values": ["current", "stale", "unknown"]}, - {"name": "REVIEWER_SOURCE_INPUT_FIELDS", "module": "loopx/capabilities/issue_fix/reviewer_recommendation.py", "container": "set", "values": ["schema_version", "sources"]}, - {"name": "REVIEWER_SOURCE_MATCH_KINDS", "module": "loopx/capabilities/issue_fix/reviewer_recommendation.py", "container": "set", "values": ["path_prefix", "path_glob", "repository_fallback"]}, - {"name": "REVIEWER_SOURCE_ROUTE_FIELDS", "module": "loopx/capabilities/issue_fix/reviewer_recommendation.py", "container": "set", "values": ["route_id", "match_kind", "pattern", "primary_reviewers", "fallback_reviewers"]}, - {"name": "REVIEWER_SOURCE_TRUST_LEVELS", "module": "loopx/capabilities/issue_fix/reviewer_recommendation.py", "container": "set", "values": ["authoritative", "verified", "advisory"]}, - {"name": "DELIVERY_ERRORS", "module": "loopx/capabilities/manager_context/roundtrip.py", "container": "set", "values": ["provider_delivery_unverified", "provider_locator_unavailable", "provider_verifier_unavailable", "provider_verification_unavailable", "provider_delivery_intent_conflict", "provider_message_missing", "provider_delivery_mismatch", "return_authorization_unavailable", "original_route_unavailable", "initial_delivery_receipt_unavailable", "original_route_or_return_delivery_unavailable", "delivery_state_unreadable"]}, - {"name": "DELIVERY_STATUSES", "module": "loopx/capabilities/manager_context/roundtrip.py", "container": "set", "values": ["queued", "retry_pending", "verification_required", "delivered", "superseded", "explicit_unverified"]}, - {"name": "PHASES", "module": "loopx/capabilities/manager_context/roundtrip.py", "container": "tuple", "values": ["decision", "conclusion"]}, - {"name": "RETURN_RESOLUTION_REASONS", "module": "loopx/capabilities/manager_context/roundtrip.py", "container": "frozenset", "values": ["return_authorization_unavailable", "original_route_unavailable", "initial_delivery_receipt_unavailable"]}, - {"name": "UNSAFE_FIELDS", "module": "loopx/capabilities/material_lifecycle/_validation.py", "container": "set", "values": ["api_key", "content", "credential", "credentials", "private_locator", "provider_payload", "raw_chat", "raw_content", "raw_provider_payload", "token", "tool_output"]}, - {"name": "MATERIAL_LIFECYCLE_STATES", "module": "loopx/capabilities/material_lifecycle/inventory.py", "container": "set", "values": ["active", "archived", "candidate", "carryover", "unread"]}, - {"name": "OBSERVATION_STATES", "module": "loopx/capabilities/pr_review_queue/core.py", "container": "set", "values": ["not_observed", "observed_unchanged", "material_transition"]}, - {"name": "DETAIL_FIELDS", "module": "loopx/capabilities/pr_review_queue/github_source.py", "container": "tuple", "values": ["body", "files", "reviewDecision", "mergeStateStatus", "createdAt", "commits", "reviews"]}, - {"name": "BEHAVIORAL_POLICY_AREAS", "module": "loopx/capabilities/pr_review_queue/review_contract.py", "container": "set", "values": ["public_entry_or_policy", "agent_instruction_surface"]}, - {"name": "CODE_AREAS", "module": "loopx/capabilities/pr_review_queue/review_contract.py", "container": "set", "values": ["product_runtime", "app_or_ui_surface", "ci_or_release", "build_or_config"]}, - {"name": "REQUIRED_FINAL_SECTIONS", "module": "loopx/capabilities/pr_review_queue/review_contract.py", "container": "list", "values": ["动机", "改动思路", "具体改动", "对主干的风险", "我的整体评价"]}, - {"name": "SEMANTIC_CANDIDATE_DECISIONS", "module": "loopx/capabilities/pr_review_queue/review_contract.py", "container": "tuple", "values": ["reuse_existing", "extend_vocabulary", "create_vocabulary", "local_only", "external_input", "compatibility_only", "unknown"]}, - {"name": "CAPABILITY_ORIGINS", "module": "loopx/capabilities/registry.py", "container": "frozenset", "values": ["builtin", "extension"]}, - {"name": "CAPABILITY_VISIBILITIES", "module": "loopx/capabilities/registry.py", "container": "frozenset", "values": ["public", "internal"]}, - {"name": "REQUIRED_CAPABILITY_FIELDS", "module": "loopx/capabilities/registry.py", "container": "tuple", "values": ["id", "title", "status", "user_value", "next_real_step"]}, - {"name": "REQUIRED_PUBLIC_CAPABILITY_FIELDS", "module": "loopx/capabilities/registry.py", "container": "tuple", "values": ["real_world_anchor", "entry_command"]}, - {"name": "CLOCK_FIELDS", "module": "loopx/capabilities/reliability_diagnostics/envelope.py", "container": "frozenset", "values": ["source", "uncertainty_ms"]}, - {"name": "CONTROL_FIELD_FAMILIES", "module": "loopx/capabilities/reliability_diagnostics/envelope.py", "container": "frozenset", "values": ["command", "commands", "send", "prompt", "inject", "schedule", "retry", "stop", "resume", "pause", "cancel", "gate", "gatedecision", "toolcall", "toolinvocation", "workerstate", "continuation", "callback", "endpoint", "outboundendpoint"]}, - {"name": "ENVELOPE_FIELDS", "module": "loopx/capabilities/reliability_diagnostics/envelope.py", "container": "frozenset", "values": ["schema_version", "capability_id", "provider_id", "observer_id", "goal_id", "session_id", "agent_id", "sequence", "observed_at", "clock", "event_kind", "summary", "source_refs"]}, - {"name": "RAW_MATERIAL_FIELD_FAMILIES", "module": "loopx/capabilities/reliability_diagnostics/envelope.py", "container": "frozenset", "values": ["transcript", "messages", "content", "text", "arguments", "result", "output", "tooloutput", "stdout", "stderr", "log", "logs", "trace", "path", "localpath", "cwd", "credential", "credentials", "token", "secret"]}, - {"name": "SUMMARY_INTEGER_FIELDS", "module": "loopx/capabilities/reliability_diagnostics/envelope.py", "container": "frozenset", "values": ["turn", "step"]}, - {"name": "SUMMARY_TOKEN_FIELDS", "module": "loopx/capabilities/reliability_diagnostics/envelope.py", "container": "frozenset", "values": ["reason", "status", "tool_name", "error_class", "source_event_type", "message_source_kind"]}, - {"name": "RUN_IDENTITY_FIELDS", "module": "loopx/capabilities/reliability_diagnostics/intake.py", "container": "frozenset", "values": ["worker_id", "model_id", "task_id", "environment_id", "tools_id", "budget_id", "adapter_revision", "observer_revision"]}, - {"name": "STATS_FIELDS", "module": "loopx/capabilities/reliability_diagnostics/intake.py", "container": "frozenset", "values": ["schema_version", "capability_id", "provider_id", "observer_id", "goal_id", "run_identity", "event_sources", "source_fields_consumed", "emitted_at", "observed_event_count", "accepted_event_count", "rejected_event_count", "rejected_by_reason", "buffer_bound", "backpressure_drop_count", "observer_failure_count", "peak_buffered_event_count", "flush_attempt_count", "outbound_endpoints", "observation_entered_worker_context", "observation_entered_scheduler_inputs", "clock_source"]}, - {"name": "BASE_HOOK_NAMES", "module": "loopx/capabilities/repository_change_window/git_hook.py", "container": "tuple", "values": ["pre-commit", "pre-push"]}, - {"name": "APPLICATION_OUTCOMES", "module": "loopx/capabilities/reward_memory/application.py", "container": "set", "values": ["applied", "ignored", "refuted", "failed", "not_available", "available_not_applied"]}, - {"name": "DURABLE_RECALL_CLASSES", "module": "loopx/capabilities/reward_memory/application.py", "container": "set", "values": ["hard_policy", "soft_preference", "procedural_experience"]}, - {"name": "RECALL_MODES", "module": "loopx/capabilities/reward_memory/application.py", "container": "set", "values": ["function_boundary", "bounded_agentic_search"]}, - {"name": "RECALL_QUERY_KINDS", "module": "loopx/capabilities/reward_memory/application.py", "container": "set", "values": ["business_recall", "ingest_verification"]}, - {"name": "BEHAVIOR_STATUSES", "module": "loopx/capabilities/reward_memory/architecture.py", "container": "set", "values": ["bug_confirmed", "by_design", "uncertain"]}, - {"name": "EDGE_CASE_COMPLEXITIES", "module": "loopx/capabilities/reward_memory/architecture.py", "container": "set", "values": ["low", "medium", "high"]}, - {"name": "MEMORY_CLASS_IDS", "module": "loopx/capabilities/reward_memory/architecture.py", "container": "tuple", "values": ["run_bound_reward", "hard_policy", "soft_preference", "procedural_experience", "working_context"]}, - {"name": "CONFIDENCE_LEVELS", "module": "loopx/capabilities/reward_memory/candidate_review.py", "container": "set", "values": ["low", "medium", "high"]}, - {"name": "CONFLICT_STATES", "module": "loopx/capabilities/reward_memory/candidate_review.py", "container": "set", "values": ["clear", "unresolved"]}, - {"name": "ELIGIBLE_POLICY_ACTOR_ROLES", "module": "loopx/capabilities/reward_memory/candidate_review.py", "container": "set", "values": ["verified_repository_core_contributor", "verified_project_owner_or_operator"]}, - {"name": "REVIEW_DECISIONS", "module": "loopx/capabilities/reward_memory/candidate_review.py", "container": "set", "values": ["accept", "edit", "reject", "retire", "no_write"]}, - {"name": "SOURCE_FRESHNESS_STATES", "module": "loopx/capabilities/reward_memory/candidate_review.py", "container": "set", "values": ["current", "stale", "unknown"]}, - {"name": "TARGET_CLASS_IDS", "module": "loopx/capabilities/reward_memory/candidate_review.py", "container": "set", "values": ["hard_policy", "soft_preference", "procedural_experience"]}, - {"name": "APPLICATION_DISPOSITIONS", "module": "loopx/capabilities/reward_memory/dogfood.py", "container": "set", "values": ["applied", "not_applied", "refuted"]}, - {"name": "DOMAIN_FAMILIES", "module": "loopx/capabilities/reward_memory/dogfood.py", "container": "set", "values": ["issue_fix", "loopx"]}, - {"name": "OPERATOR_ACTIONS", "module": "loopx/capabilities/reward_memory/dogfood.py", "container": "set", "values": ["edit", "retire"]}, - {"name": "UTILITY_EVALUATION_STATUSES", "module": "loopx/capabilities/reward_memory/dogfood.py", "container": "set", "values": ["accepted", "rejected", "not_requested"]}, - {"name": "REQUIRED_CASE_IDS", "module": "loopx/capabilities/reward_memory/evaluation.py", "container": "tuple", "values": ["compact_restart_survival", "project_module_scope_isolation", "supersede_revoke_rejection", "stale_source_rejection", "multi_person_authority_conditions", "gate_non_override", "candidate_ranking_influence", "large_edge_case_patch_protection"]}, - {"name": "ATTRIBUTION_LEVELS", "module": "loopx/capabilities/reward_memory/memory_utility.py", "container": "frozenset", "values": ["item", "set", "none"]}, - {"name": "EVIDENCE_BASES", "module": "loopx/capabilities/reward_memory/memory_utility.py", "container": "frozenset", "values": ["owner_correction", "controlled_replay", "deterministic_effect", "evaluator_inference", "insufficient"]}, - {"name": "UTILITY_LABELS", "module": "loopx/capabilities/reward_memory/memory_utility.py", "container": "frozenset", "values": ["helpful", "harmful", "neutral", "unknown"]}, - {"name": "FRESHNESS_MODES", "module": "loopx/capabilities/reward_memory/registry.py", "container": "set", "values": ["source_truth_bound", "revision_bound", "time_bound", "session_archive_bound", "execution_bound"]}, - {"name": "IDENTITY_SCOPE_FIELDS", "module": "loopx/capabilities/reward_memory/registry.py", "container": "tuple", "values": ["user_ref", "peer_ref", "session_ref"]}, - {"name": "LIFECYCLE_STATES", "module": "loopx/capabilities/reward_memory/registry.py", "container": "set", "values": ["active", "superseded", "retired"]}, - {"name": "READ_AUTHORITIES", "module": "loopx/capabilities/reward_memory/registry.py", "container": "set", "values": ["goal_run_scoped", "authority_scoped", "module_scoped", "actor_scoped", "session_scoped"]}, - {"name": "VISIBILITIES", "module": "loopx/capabilities/reward_memory/registry.py", "container": "set", "values": ["private", "workspace", "public_safe"]}, - {"name": "WRITE_AUTHORITIES", "module": "loopx/capabilities/reward_memory/registry.py", "container": "set", "values": ["append_only_overlay", "authorized_policy_source", "provider_managed", "read_only", "ephemeral_runtime"]}, - {"name": "ALLOWED_ACCESS_MODES", "module": "loopx/capabilities/value_connectors/planner.py", "container": "set", "values": ["public_metadata_only", "agent_owned_identity", "private_metadata_gate", "external_write_gated", "fixture_only"]}, - {"name": "ALLOWED_CONNECTOR_KINDS", "module": "loopx/capabilities/value_connectors/planner.py", "container": "set", "values": ["github_channel", "botmail_identity", "community_channel", "x_public_channel", "browser_social_channel", "signup_probe", "lead_monitor", "custom_connector"]}, - {"name": "ALLOWED_GATE_STATUSES", "module": "loopx/capabilities/value_connectors/planner.py", "container": "set", "values": ["blocked_until_explicit_approval", "approved_for_this_exact_call", "denied", "needs_revision"]}, - {"name": "ALLOWED_STAGES", "module": "loopx/capabilities/value_connectors/planner.py", "container": "set", "values": ["observe", "account_setup", "draft", "external_write_request", "monitor", "value_attribution"]}, - {"name": "ALLOWED_VALUE_AXES", "module": "loopx/capabilities/value_connectors/planner.py", "container": "set", "values": ["revenue", "cost_reduction", "demand", "capability"]}, - {"name": "FORBIDDEN_TEXT_SNIPPETS", "module": "loopx/capabilities/value_connectors/planner.py", "container": "tuple", "values": ["restricted-value", "sensitive-value", "bearer "]}, - {"name": "RAW_OR_PRIVATE_KEY_HINTS", "module": "loopx/capabilities/value_connectors/planner.py", "container": "tuple", "values": ["body", "chat", "cookie", "auth material", "dm", "local_path", "log", "message_body", "password", "raw", "secret", "token", "transcript"]}, - {"name": "SOURCE_PROFILE_IDS", "module": "loopx/capabilities/value_connectors/source_map.py", "container": "set", "values": ["all", "github_public_channel", "github_public_reply_monitor", "content_ops_public_handle", "social_browser_x", "agent_reach_ops_source_map", "finance_market_snapshot"]}, - {"name": "CHAT_PROTECTED_ACTION_OPERATIONS", "module": "loopx/chat.py", "container": "frozenset", "values": ["merge", "release", "deploy", "delete", "payment"]}, - {"name": "ACTION_KINDS", "module": "loopx/chat_action_store.py", "container": "set", "values": ["goal.create", "goal.update", "goal.lifecycle", "todo.create", "todo.update", "agent.bind", "heartbeat.bind", "monitor.create", "monitor.update", "gate.resolve", "run.correct", "operation.execute"]}, - {"name": "OPERATION_LIFECYCLE_STATES", "module": "loopx/chat_action_store.py", "container": "set", "values": ["awaiting_confirmation", "claimed", "outcome_observed"]}, - {"name": "PROPOSAL_STATES", "module": "loopx/chat_action_store.py", "container": "set", "values": ["preview_ready", "applying", "gated", "failed", "rejected", "deferred", "cancelled", "stale", "applied"]}, - {"name": "RETRYABLE_PROPOSAL_STATES", "module": "loopx/chat_action_store.py", "container": "set", "values": ["preview_ready", "gated", "failed", "deferred"]}, - {"name": "SUPPORTED_ACTION_KINDS", "module": "loopx/chat_actions.py", "container": "set", "values": ["todo.create", "todo.update", "run.correct", "goal.create", "goal.update", "goal.lifecycle", "agent.bind", "heartbeat.bind", "monitor.create", "monitor.update", "gate.resolve", "operation.execute"]}, - {"name": "CHAT_IMAGE_TYPES", "module": "loopx/chat_attachments.py", "container": "set", "values": ["image/gif", "image/jpeg", "image/png", "image/webp"]}, - {"name": "SUPPORTED_LOCATIONS", "module": "loopx/chat_endpoints.py", "container": "set", "values": ["local", "remote"]}, - {"name": "RESUMABLE_SESSION_STATES", "module": "loopx/chat_store.py", "container": "set", "values": ["ready", "busy", "stale", "resuming"]}, - {"name": "TERMINAL_TURN_STATES", "module": "loopx/chat_store.py", "container": "set", "values": ["completed", "interrupted", "timed_out", "failed"]}, - {"name": "CLAUDE_LEGACY_SCHEDULER_ARGS", "module": "loopx/claude_goal_mode/hooks/goal_policy.py", "container": "list", "values": ["--host-surface", "claude_code", "--scheduler-owner", "agent_cli_loop", "--execution-mode", "interactive"]}, - {"name": "CLAUDE_RUNTIME_PROFILE_ARGS", "module": "loopx/claude_goal_mode/hooks/goal_policy.py", "container": "list", "values": ["--runtime-profile", "claude_code"]}, - {"name": "DESTRUCTIVE", "module": "loopx/claude_goal_mode/hooks/goal_policy.py", "container": "tuple", "values": ["rm -rf", "rm -fr", "mkfs", "dd if=", ":(){", "shutdown", "reboot", "git push --force", "git reset --hard", "> /dev/sd", "format "]}, - {"name": "READONLY_TOOLS", "module": "loopx/claude_goal_mode/hooks/goal_policy.py", "container": "set", "values": ["Read", "Glob", "Grep", "NotebookRead", "TodoWrite", "WebFetch", "WebSearch", "ToolSearch"]}, - {"name": "WRITE_TOOLS", "module": "loopx/claude_goal_mode/hooks/goal_policy.py", "container": "set", "values": ["Edit", "Write", "MultiEdit", "NotebookEdit"]}, - {"name": "REGISTRY_DIRS", "module": "loopx/claude_goal_mode/hooks/goal_state.py", "container": "tuple", "values": [".loopx", ".goal-harness"]}, - {"name": "BENCHMARK_TOOLKIT_COMMANDS", "module": "loopx/cli_commands/benchmark_boundary.py", "container": "set", "values": ["candidate-source-boundary", "classify-artifacts", "integrity-qualification", "four-arm-contract", "runtime-continuity", "runtime-observation", "source-revision-fence", "traex-evidence", "treatment-continuation-receipt", "verify-verifier-reward"]}, - {"name": "BENCHMARK_CONCURRENCY_COMMANDS", "module": "loopx/cli_commands/benchmark_concurrency.py", "container": "set", "values": ["concurrency-status", "concurrency-configure", "concurrency-admit", "concurrency-release", "concurrency-tune"]}, - {"name": "BENCHMARK_EXPERIMENT_BOARD_COMMANDS", "module": "loopx/cli_commands/benchmark_experiment_board.py", "container": "set", "values": ["experiment-board-show", "experiment-board-reconcile", "experiment-board-upsert"]}, - {"name": "BENCHMARK_STUDY_COMMANDS", "module": "loopx/cli_commands/benchmark_study.py", "container": "set", "values": ["study-validate", "upload-envelope", "upload-local", "upload-readback", "study-dashboard", "behavior-report"]}, - {"name": "FEISHU_SINK_EXPLORE_COMMANDS", "module": "loopx/cli_commands/explore_feishu_commands.py", "container": "frozenset", "values": ["feishu-setup", "feishu-visual-configure", "feishu-sync", "feishu-card"]}, - {"name": "PROJECT_LIFECYCLE_COMMANDS", "module": "loopx/cli_commands/project_lifecycle.py", "container": "set", "values": ["refresh-state", "read-only-map", "reward", "operator-gate"]}, - {"name": "QUOTA_SCHEDULER_COMMANDS", "module": "loopx/cli_commands/quota_context.py", "container": "frozenset", "values": ["should-run", "monitor-poll", "scheduler-ack", "scheduler-ack-current", "scheduler-fail-current", "spend-slot"]}, - {"name": "QUOTA_SCHEDULER_FOLLOWUP_COMMANDS", "module": "loopx/cli_commands/quota_context.py", "container": "frozenset", "values": ["scheduler-ack", "scheduler-ack-current", "scheduler-fail-current"]}, - {"name": "QUOTA_SHOULD_RUN_DETAIL_SECTIONS", "module": "loopx/cli_commands/quota_request.py", "container": "tuple", "values": ["scheduler", "agent-todos", "user-todos", "goal-boundary", "vision"]}, - {"name": "REGISTRY_LIFECYCLE_COMMANDS", "module": "loopx/cli_commands/registry_admin_lifecycle.py", "container": "set", "values": ["archive-runtime", "retire-global-goal", "uninstall-project", "sync-global", "migrate-state"]}, - {"name": "REGISTRY_AUTHORITY_COMMANDS", "module": "loopx/cli_commands/registry_authority.py", "container": "set", "values": ["register-authority-source", "import-doc-registry-authority"]}, - {"name": "SUPERVISOR_CONTROL_COMMANDS", "module": "loopx/cli_commands/support_control_supervisor.py", "container": "set", "values": ["supervisor-event", "supervisor-observe", "supervisor-prompt"]}, - {"name": "PLANNED_TURN_HOST_CHOICES", "module": "loopx/cli_commands/turn_registration.py", "container": "list", "values": ["codex-cli", "claude-code", "dsh", "generic-cli"]}, - {"name": "RUN_ONCE_TURN_HOST_CHOICES", "module": "loopx/cli_commands/turn_registration.py", "container": "list", "values": ["codex-cli", "dsh", "generic-cli"]}, - {"name": "WORKER_BRIDGE_COMMANDS", "module": "loopx/cli_commands/worker_bridge.py", "container": "set", "values": ["active-user-codex-simulator-contract", "active-user-contract", "active-user-intervention", "active-user-observe", "active-user-simulator-output", "contract", "attached-session-bind", "attached-session-claim", "attached-session-complete", "attached-session-list"]}, - {"name": "GLOBAL_OPTIONS_WITH_VALUE", "module": "loopx/cli_runtime.py", "container": "frozenset", "values": ["--registry", "--runtime-root", "--format"]}, - {"name": "MULTI_SUBAGENT_FEATURE_CHOICES", "module": "loopx/configure_goal.py", "container": "tuple", "values": ["off", "enabled"]}, - {"name": "WAITING_ON_CHOICES", "module": "loopx/configure_goal.py", "container": "tuple", "values": ["codex", "user_or_controller", "controller", "external_evidence"]}, - {"name": "DEFAULT_SCAN_SUFFIXES", "module": "loopx/contract.py", "container": "set", "values": [".md", ".py", ".toml", ".json", ".yaml", ".yml", ".sh"]}, - {"name": "DEFAULT_SKIP_DIRS", "module": "loopx/contract.py", "container": "set", "values": [".git", ".goal-harness", ".loopx", ".goal-wrapper.local", ".local", ".venv", "__pycache__", ".pytest_cache", ".ruff_cache", "build", "dist", "node_modules", "runtime"]}, - {"name": "LOCAL_PRIVATE_STATE_FILE_NAMES", "module": "loopx/contract.py", "container": "set", "values": ["ACTIVE_GOAL_STATE.md", "ACTIVE_GOAL_STATE.md.lock"]}, - {"name": "LOCAL_PRIVATE_STATE_PARTS", "module": "loopx/contract.py", "container": "set", "values": [".codex", ".goal-harness", ".goal-wrapper.local", ".local", ".loopx"]}, - {"name": "CAPABILITY_OWNER_GATE_HINTS", "module": "loopx/control_plane/agents/capability_gate.py", "container": "set", "values": ["credentials", "production_access"]}, - {"name": "DELIVERY_WORKSPACE_IDENTITY_KINDS", "module": "loopx/control_plane/agents/delivery_workspace.py", "container": "frozenset", "values": ["git_repository", "local_goal"]}, - {"name": "DELIVERY_WORKSPACE_KINDS", "module": "loopx/control_plane/agents/delivery_workspace.py", "container": "frozenset", "values": ["canonical_checkout", "independent_git_worktree", "local_goal_workspace"]}, - {"name": "LEGACY_HIERARCHY_ROLES", "module": "loopx/control_plane/agents/legacy_migration.py", "container": "set", "values": ["primary-agent", "side-agent"]}, - {"name": "AGENT_PROFILE_FIELDS", "module": "loopx/control_plane/agents/profile.py", "container": "set", "values": ["schema_version", "agent_id", "profile_role", "scope_summary", "default_task_classes", "vision_requirement", "preferred_action_kinds", "avoid_action_kinds"]}, - {"name": "AGENT_PROFILE_HIERARCHY_ROLES", "module": "loopx/control_plane/agents/profile.py", "container": "set", "values": ["leader", "manager", "supervisor", "worker"]}, - {"name": "AGENT_PROFILE_VISION_REQUIREMENTS", "module": "loopx/control_plane/agents/profile.py", "container": "set", "values": ["optional", "required"]}, - {"name": "PEER_WRITE_ACTION_KINDS", "module": "loopx/control_plane/agents/workspace_guard.py", "container": "set", "values": ["fix", "implement", "rebuild", "repair", "writeback"]}, - {"name": "LOCAL_AUTHORITY_SOURCES", "module": "loopx/control_plane/coordination/local_authority.py", "container": "tuple", "values": ["file_v0", "sqlite_v0"]}, - {"name": "OBSERVED_LIFECYCLE_FLAGS", "module": "loopx/control_plane/goals/acceptance_observation.py", "container": "frozenset", "values": ["connected", "mapped", "refreshed", "adapter_inspected", "run_recorded", "reward_judged", "operator_approved", "controller_ready"]}, - {"name": "AGENT_TODO_HEADER_MARKERS", "module": "loopx/control_plane/goals/active_state_metadata.py", "container": "tuple", "values": ["agent todo", "codex todo", "project agent todo"]}, - {"name": "TODO_ARCHIVE_HEADER_MARKERS", "module": "loopx/control_plane/goals/active_state_metadata.py", "container": "tuple", "values": ["todo archive", "work archive", "completed archive", "completed work", "完成归档", "待办归档"]}, - {"name": "USER_TODO_HEADER_MARKERS", "module": "loopx/control_plane/goals/active_state_metadata.py", "container": "tuple", "values": ["user todo", "owner review reading queue", "owner reading queue"]}, - {"name": "GOAL_AMENDMENT_CLASSES", "module": "loopx/control_plane/goals/goal_amendment_proposal.py", "container": "tuple", "values": ["lane_route", "shared_work_graph", "shared_acceptance", "protected_authority"]}, - {"name": "GOAL_AMENDMENT_PROPOSAL_ADMISSIONS", "module": "loopx/control_plane/goals/goal_amendment_proposal.py", "container": "tuple", "values": ["admitted", "needs_rebase"]}, - {"name": "GOAL_AMENDMENT_PROPOSAL_ADMISSION_FACTS", "module": "loopx/control_plane/goals/goal_amendment_proposal.py", "container": "tuple", "values": ["base_state_event_basis_sequence_behind_derived_head", "base_source_basis_digest_mismatch", "base_source_basis_unverifiable", "base_revision_basis_superseded"]}, - {"name": "RAW_MATERIAL_KEY_HINTS", "module": "loopx/control_plane/goals/goal_channel_projection.py", "container": "tuple", "values": ["credential", "local_path", "log", "raw", "secret", "stderr", "stdout", "token", "trace", "transcript"]}, - {"name": "VISION_FALLBACK_DECLARATION_FIELDS", "module": "loopx/control_plane/goals/goal_frontier/fallback_disposition.py", "container": "tuple", "values": ["target_todo_id", "successor_todo_id"]}, - {"name": "VISION_TODO_DELTA_SUCCESSOR_ACTIONS", "module": "loopx/control_plane/goals/goal_frontier/fallback_disposition.py", "container": "frozenset", "values": ["create", "reopen"]}, - {"name": "VISION_OUTCOME_CHECKPOINT_CONTINUATION_OUTCOMES", "module": "loopx/control_plane/goals/goal_frontier/outcome_continuity.py", "container": "set", "values": ["continue", "no_change", "replan"]}, - {"name": "VISION_OUTCOME_CHECKPOINT_MATERIAL_OUTCOMES", "module": "loopx/control_plane/goals/goal_frontier/outcome_continuity.py", "container": "set", "values": ["outcome_gap", "outcome_progress", "primary_goal_outcome"]}, - {"name": "VISION_CHECKPOINT_SATISFIED_DECISIONS", "module": "loopx/control_plane/goals/goal_frontier/semantic_history.py", "container": "set", "values": ["patched", "unchanged_with_reason"]}, - {"name": "GOAL_PATH_DELTA_OUTCOMES", "module": "loopx/control_plane/goals/goal_vision.py", "container": "frozenset", "values": ["continue", "replan", "wait", "no_change", "ask_human", "stop"]}, - {"name": "GOAL_VISION_BUDGET_COMPACT_FIELDS", "module": "loopx/control_plane/goals/goal_vision.py", "container": "tuple", "values": ["schema_version", "status", "field_usage", "total_limit", "total_usage"]}, - {"name": "GOAL_VISION_FALLBACK_DECLARATION_FIELDS", "module": "loopx/control_plane/goals/goal_vision.py", "container": "tuple", "values": ["target_todo_id", "successor_todo_id"]}, - {"name": "VISION_FRONTIER_TODO_DELTA_ACTIONS", "module": "loopx/control_plane/goals/goal_vision_read_model.py", "container": "frozenset", "values": ["activate", "create", "reopen", "resume", "retain"]}, - {"name": "GOAL_VISION_CLOSED_STATES", "module": "loopx/control_plane/goals/goal_vision_state.py", "container": "frozenset", "values": ["vision_closed", "retired", "retired_or_superseded", "superseded", "no_followup"]}, - {"name": "HANDOFF_ADVANCEMENT_TASK_CLASSES", "module": "loopx/control_plane/handoff/review_packet_context.py", "container": "set", "values": ["advancement_task", "execution_task", "delivery_task"]}, - {"name": "HANDOFF_MONITOR_MARKERS", "module": "loopx/control_plane/handoff/review_packet_context.py", "container": "tuple", "values": ["monitor", "observation", "readiness", "watch", "poll", "dependency monitor", "观察", "监控", "等待"]}, - {"name": "HANDOFF_MONITOR_TASK_CLASSES", "module": "loopx/control_plane/handoff/review_packet_context.py", "container": "set", "values": ["blocker", "continuous_monitor", "monitor", "user_gate"]}, - {"name": "VISIBLE_GOAL_HOST_CONTROL_CAPABILITIES", "module": "loopx/control_plane/heartbeat/visible_goal.py", "container": "frozenset", "values": ["automation_update", "current_time", "first_turn_receipt", "heartbeat_prequota", "loop", "loopx_turn", "rrule", "scheduler_execution_context", "turn_instance_id"]}, - {"name": "POST_WRITEBACK_COMPOSITION_RETRY_ERROR_CODES", "module": "loopx/control_plane/post_writeback_composition_retry.py", "container": "tuple", "values": ["source_projection_failed", "dispatch_failed"]}, - {"name": "PROJECT_BINDING_FIELDS", "module": "loopx/control_plane/projects/contract.py", "container": "tuple", "values": ["repository_bindings", "external_locator_bindings"]}, - {"name": "PROJECT_KINDS", "module": "loopx/control_plane/projects/registry.py", "container": "tuple", "values": ["work", "personal"]}, - {"name": "HEARTBEAT_HANDOFF_READINESS_COMPACT_FIELDS", "module": "loopx/control_plane/quota/heartbeat_recommendation.py", "container": "tuple", "values": ["ready", "codex_ready", "source", "quota_state", "handoff_status", "post_handoff_run_seen", "post_handoff_small_scale_streak", "post_handoff_outcome_gap_streak", "handoff_interface_budget"]}, - {"name": "HEARTBEAT_POST_HANDOFF_RUN_COMPACT_FIELDS", "module": "loopx/control_plane/quota/heartbeat_recommendation.py", "container": "tuple", "values": ["generated_at", "classification", "progress_scope", "delivery_batch_scale", "delivery_outcome", "delivery_turn_kind", "health_check", "json_exists", "markdown_exists"]}, - {"name": "AUTONOMOUS_CANDIDATE_CONTEXT_FIELDS", "module": "loopx/control_plane/quota/policy_constants.py", "container": "tuple", "values": ["source", "open_count", "task_class", "items"]}, - {"name": "FOCUS_WAIT_LIFECYCLE_MARKERS", "module": "loopx/control_plane/quota/policy_constants.py", "container": "set", "values": ["continuation_boundary", "focus_wait"]}, - {"name": "SELF_REPAIR_SPEND_ACTIONS", "module": "loopx/control_plane/quota/policy_constants.py", "container": "set", "values": ["control_plane_health_repair", "control_plane_projection_repair", "state_projection_gap_repair", "boundary_projection_repair", "todo_decision_scope_projection_repair"]}, - {"name": "SELECTED_TODO_AGENT_FIELDS", "module": "loopx/control_plane/quota/selected_todo_projection.py", "container": "tuple", "values": ["agent_id", "selected_by", "confidence", "claim_required_before_work", "selection_binding", "selection_reason", "delivery_boundary"]}, - {"name": "SELECTED_TODO_COMPACT_FIELDS", "module": "loopx/control_plane/quota/selected_todo_projection.py", "container": "tuple", "values": ["todo_id", "index", "role", "priority", "status", "task_class", "action_kind", "task_domain", "capability_binding_ref", "task_repository", "continuation_policy", "required_write_scopes", "required_capabilities", "claimed_by", "bound_agent", "goal_bound", "blocks_agent", "excluded_agents", "unblocks_todo_id", "target_key", "next_due_at", "expires_at"]}, - {"name": "WORK_LANE_SELECTED_TODO_ITEM_FIELDS", "module": "loopx/control_plane/quota/selected_todo_projection.py", "container": "tuple", "values": ["monitor_due_items", "monitor_schedule_gap_items", "resume_blocked_by_monitor_items"]}, - {"name": "DELIVERY_WORKSPACE_REQUIREMENTS", "module": "loopx/control_plane/quota/settlement_workspace_causality.py", "container": "frozenset", "values": ["required", "not_required", "unknown"]}, - {"name": "RUNTIME_RECOVERY_ACTION_TOKENS", "module": "loopx/control_plane/quota/stall_repair.py", "container": "frozenset", "values": ["configure", "execute", "install", "launch", "materialize", "rebuild", "repair", "restore", "retry", "run", "start"]}, - {"name": "STALL_HEALTH_ITEM_COMPACT_FIELDS", "module": "loopx/control_plane/quota/stall_repair.py", "container": "tuple", "values": ["goal_id", "status", "waiting_on", "severity", "source", "recommended_action"]}, - {"name": "QUOTA_STATE_ORDER", "module": "loopx/control_plane/quota/states.py", "container": "tuple", "values": ["blocked_health", "operator_gate", "focus_wait", "eligible", "waiting", "throttled", "paused"]}, - {"name": "READ_ONLY_ACTION_KINDS", "module": "loopx/control_plane/quota/task_orchestration_admission.py", "container": "frozenset", "values": ["analyze", "compare", "inspect", "review", "test", "validate"]}, - {"name": "USAGE_METRIC_NAMES", "module": "loopx/control_plane/quota/usage_summary.py", "container": "tuple", "values": ["input_tokens", "output_tokens", "cache_tokens", "cost_usd", "duration_ms"]}, - {"name": "QUOTA_VOID_COMMIT_STATUSES", "module": "loopx/control_plane/quota/void_commit.py", "container": "frozenset", "values": ["preview", "not_found", "written", "replayed", "repaired", "conflict"]}, - {"name": "DECISION_FRESHNESS_CLASSIFICATION_PREFIXES", "module": "loopx/control_plane/runtime/decision_freshness.py", "container": "tuple", "values": ["human_reward", "reward_overlay"]}, - {"name": "EVENT_LEDGER_CLASSES", "module": "loopx/control_plane/runtime/event_ledger.py", "container": "tuple", "values": ["accounting", "decision", "evidence", "state", "work"]}, - {"name": "LOOPX_COMMAND_RECORD_ALLOWED_SUBCOMMANDS", "module": "loopx/control_plane/runtime/public_safety.py", "container": "set", "values": ["start-goal", "quota should-run", "todo add", "todo claim", "todo update", "todo complete", "refresh-state", "quota spend-slot", "status", "diagnose"]}, - {"name": "CONTROLLER_READINESS_COMPACT_FIELDS", "module": "loopx/control_plane/runtime/run_compaction.py", "container": "tuple", "values": ["classification", "read_only_observer_ready", "decision_advisor_ready", "write_controller_ready", "missing_gates", "review_judgment", "next_handoff_condition"]}, - {"name": "CONTROLLER_READINESS_GATE_FIELDS", "module": "loopx/control_plane/runtime/run_compaction.py", "container": "tuple", "values": ["id", "ok", "review"]}, - {"name": "HUMAN_REWARD_COMPACT_FIELDS", "module": "loopx/control_plane/runtime/run_compaction.py", "container": "tuple", "values": ["recorded_at", "decision", "reward", "reason_summary", "follow_up", "lesson"]}, - {"name": "OPERATOR_GATE_COMPACT_FIELDS", "module": "loopx/control_plane/runtime/run_compaction.py", "container": "tuple", "values": ["recorded_at", "gate", "decision", "operator_question", "reason_summary", "follow_up", "agent_command"]}, - {"name": "OPERATOR_GATE_RESUME_CONTRACT_COMPACT_FIELDS", "module": "loopx/control_plane/runtime/run_compaction.py", "container": "tuple", "values": ["version", "goal_id", "run_id", "gate_id", "created_state_ref", "created_policy_version", "allowed_decisions", "operator_decision", "latest_state_ref", "freshness_check", "precondition_check", "migration_or_rebase_result", "resulting_action", "validation_after_resume"]}, - {"name": "QUOTA_MONITOR_TARGET_COMPACT_FIELDS", "module": "loopx/control_plane/runtime/run_compaction.py", "container": "tuple", "values": ["schema_version", "target_id", "monitor_mode", "effective_action", "agent_id", "frontier_identity"]}, - {"name": "RUN_BASE_COMPACT_FIELDS", "module": "loopx/control_plane/runtime/run_compaction.py", "container": "tuple", "values": ["generated_at", "run_id", "turn_instance_id", "replan_obligation_id", "goal_id", "parent_run_id", "spawned_by_goal_id", "agent_role", "classification", "agent_id", "agent_lane", "progress_scope", "todo_id", "progress_observation", "delivery_batch_scale", "delivery_outcome", "lifecycle_phase", "lifecycle_flags", "recommended_action", "health_check", "result_status", "approval_state", "active_task_count", "active_priorities", "cache_check", "project_map", "json_exists", "markdown_exists", "usage"]}, - {"name": "VISION_CHECKPOINT_COMPACT_FIELDS", "module": "loopx/control_plane/runtime/run_compaction.py", "container": "tuple", "values": ["schema_version", "agent_id", "required", "satisfied", "decision", "delivery_boundary", "agent_vision_state", "unchanged_reason", "missing_baseline"]}, - {"name": "VISION_CHECKPOINT_CONTINUITY_BASIS_FIELDS", "module": "loopx/control_plane/runtime/run_compaction.py", "container": "tuple", "values": ["kind", "vision_generated_at"]}, - {"name": "VISION_CHECKPOINT_TRIGGER_FIELDS", "module": "loopx/control_plane/runtime/run_compaction.py", "container": "tuple", "values": ["kind", "delivery_outcome", "todo_id"]}, - {"name": "OUTCOME_VISION_CHECKPOINT_TRIGGER_KINDS", "module": "loopx/control_plane/runtime/run_context_retention.py", "container": "set", "values": ["autonomous_replan_recorded", "material_delivery_outcome"]}, - {"name": "OWNER_CORRECTION_RUN_PAYLOAD_FIELDS", "module": "loopx/control_plane/runtime/run_context_retention.py", "container": "tuple", "values": ["generated_at", "agent_id", "classification", "human_reward"]}, - {"name": "SEMANTIC_CONTEXT_RUN_FIELDS", "module": "loopx/control_plane/runtime/run_context_retention.py", "container": "tuple", "values": ["latest_agent_vision_run", "latest_vision_checkpoint_run", "latest_outcome_vision_checkpoint_run", "latest_autonomous_replan_ack_run", "latest_replan_ack_feedback_run", "latest_material_milestone_run", "latest_evidence_delivery_run"]}, - {"name": "REWARD_OVERLAY_IDENTITY_KEYS", "module": "loopx/control_plane/runtime/run_index_duplicates.py", "container": "tuple", "values": ["generated_at", "goal_id", "classification", "json_path", "markdown_path"]}, - {"name": "SESSION_RUNTIME_READONLY_PROJECTION_KEYS", "module": "loopx/control_plane/runtime/session_runtime.py", "container": "tuple", "values": ["session_runtime_readonly_projection", "session_runtime_projection"]}, - {"name": "MATERIAL_PROJECTION_KINDS", "module": "loopx/control_plane/runtime/shared_runtime_material_projection.py", "container": "set", "values": ["dreaming_decision", "operator_gate_decision", "read_only_project_map"]}, - {"name": "CODEX_READY_CLASSIFICATIONS", "module": "loopx/control_plane/runtime/status_classifications.py", "container": "set", "values": ["controller_opted_in_waiting_for_run", "design_next_experiment", "inspect_eval_result", "inspect_result", "needs_more_read_only_evidence", "needs_validation", "public_harness_healthy", "read_only_project_map", "run_validation", "state_refreshed", "operator_gate_approved", "monitor_todo_repeat_dedupe_deployed"]}, - {"name": "DREAMING_ADVISORY_CLASSIFICATIONS", "module": "loopx/control_plane/runtime/status_classifications.py", "container": "set", "values": ["dreaming_exploration_proposal", "dreaming_memory_consolidation", "dreaming_refactor_warning", "dreaming_archive_suggestion"]}, - {"name": "HANDOFF_READY_CLASSIFICATIONS", "module": "loopx/control_plane/runtime/status_classifications.py", "container": "set", "values": ["operator_gate_approved", "controller_opted_in_waiting_for_run"]}, - {"name": "AUTHORITY_CHANGE_CLASSIFICATIONS", "module": "loopx/control_plane/runtime/stride_observation.py", "container": "frozenset", "values": ["bounded_replan_progress", "operator_gate_approved", "operator_gate_rejected", "operator_gate_deferred"]}, - {"name": "GOAL_RUNTIME_DEFER_ACTIONS", "module": "loopx/control_plane/scheduler/execution_context.py", "container": "frozenset", "values": ["backoff_agent_monitor_only", "backoff_until_fresh_evidence", "backoff_until_material_transition", "backoff_until_reassigned", "backoff_until_state_change", "backoff_waiting_for_user", "repair_interaction_contract_projection"]}, - {"name": "DEFAULT_ACK_CAPABILITIES", "module": "loopx/control_plane/scheduler/scheduler_hint.py", "container": "set", "values": ["shell", "filesystem_read", "filesystem_write"]}, - {"name": "SCHEDULER_BASE_IDENTITY_KEYS", "module": "loopx/control_plane/scheduler/scheduler_hint.py", "container": "tuple", "values": ["goal_id", "agent_identity.agent_id", "effective_action", "heartbeat_recommendation.recommended_mode", "interaction_contract.mode"]}, - {"name": "SCHEDULER_FRONTIER_IDENTITY_KEYS", "module": "loopx/control_plane/scheduler/scheduler_hint.py", "container": "tuple", "values": ["selected_todo.todo_id", "selected_todo.action_kind", "selected_todo.target_key", "selected_todo.claimed_by", "selected_todo.capability_binding_ref"]}, - {"name": "BACKLOG_HYGIENE_SECTION_HEADINGS", "module": "loopx/control_plane/status/active_state_projection.py", "container": "tuple", "values": ["Next Action", "Operating Lessons"]}, - {"name": "AUTONOMOUS_RUN_HISTORY_NEUTRAL_CLASSIFICATIONS", "module": "loopx/control_plane/status/autonomous_replan_projection.py", "container": "set", "values": ["quota_slot_spent", "quota_slot_voided", "delivery_completion_spend_accounted_v0"]}, - {"name": "CONNECTED_ADAPTER_STATUSES", "module": "loopx/control_plane/status/goal_attention_projection.py", "container": "set", "values": ["connected", "connected-read-only", "pre-tick-runnable"]}, - {"name": "LEGACY_EXTERNAL_EVIDENCE_CLASSIFICATION_PREFIXES", "module": "loopx/control_plane/status/goal_attention_projection.py", "container": "tuple", "values": ["await_", "external_evidence_observation_"]}, - {"name": "REGISTRY_WAITING_ON_OVERRIDES", "module": "loopx/control_plane/status/goal_attention_projection.py", "container": "set", "values": ["user_or_controller", "controller", "codex", "external_evidence"]}, - {"name": "CONNECTED_ADAPTER_STATUSES", "module": "loopx/control_plane/status/lifecycle_projection.py", "container": "set", "values": ["connected", "connected-read-only", "pre-tick-runnable"]}, - {"name": "LIFECYCLE_PRIORITY", "module": "loopx/control_plane/status/lifecycle_projection.py", "container": "tuple", "values": ["controller_ready", "reward_judged", "operator_approved", "controller_gated", "operator_gated", "adapter_inspected", "mapped", "refreshed", "connected", "registered", "planned", "run_recorded"]}, - {"name": "SOURCE_REGISTRY_SHADOW_FINDINGS", "module": "loopx/control_plane/status/registry_health_projection.py", "container": "set", "values": ["source_registry_missing", "stale_source_registry"]}, - {"name": "PLANNING_HORIZON_STRATEGIC_CONTEXT_ATTENTION_IDS", "module": "loopx/control_plane/testing/action_portfolio_scenarios.py", "container": "tuple", "values": ["todo_per_model_tests", "todo_runtime_admission", "todo_allowlist_policy"]}, - {"name": "PLANNING_HORIZON_STRATEGIC_CONTEXT_TODO_IDS", "module": "loopx/control_plane/testing/action_portfolio_scenarios.py", "container": "tuple", "values": ["todo_regression_gate", "todo_per_model_tests", "todo_runtime_admission", "todo_allowlist_policy", "todo_facts_source"]}, - {"name": "DEFAULT_REGISTERED_AGENTS", "module": "loopx/control_plane/testing/authority_e2e_fixtures.py", "container": "tuple", "values": ["agent-a", "agent-b"]}, - {"name": "RUNTIME_ROOT_BINDINGS", "module": "loopx/control_plane/testing/authority_e2e_fixtures.py", "container": "tuple", "values": ["registry", "cli_override", "cli_override_divergent"]}, - {"name": "FILE_MATRIX_ROWS", "module": "loopx/control_plane/testing/authority_e2e_ladder.py", "container": "tuple", "values": ["same_todo_one_winner", "independent_todo_applies", "replay_returns_original_receipt", "identity_mismatch_rejected", "stale_revision_conflicts", "lost_response_recovers_receipt", "receipts_retained", "authority_revision_advanced_twice", "renew_extends_the_active_lease", "expired_lease_reclaimed_with_new_epoch", "superseded_executor_cannot_write_back", "complete_creates_claimable_successor_atomically"]}, - {"name": "GATES", "module": "loopx/control_plane/testing/authority_e2e_ladder.py", "container": "tuple", "values": ["deterministic", "env:postgresql", "env:nokv_authority", "env:nokv_legacy"]}, - {"name": "NOKV_SECRET_VARIABLES", "module": "loopx/control_plane/testing/authority_e2e_ladder.py", "container": "tuple", "values": ["NOKV_OBJECT_KEY", "NOKV_OBJECT_SECRET"]}, - {"name": "NOKV_STACK_VARIABLES", "module": "loopx/control_plane/testing/authority_e2e_ladder.py", "container": "tuple", "values": ["NOKV_ETCD", "NOKV_ETCD_PREFIX", "NOKV_ROOT_ID", "NOKV_BUCKET", "NOKV_OBJECT_ENDPOINT", "NOKV_OBJECT_ROOT", "NOKV_OBJECT_KEY", "NOKV_OBJECT_SECRET"]}, - {"name": "PRODUCT_PATHS", "module": "loopx/control_plane/testing/authority_e2e_ladder.py", "container": "tuple", "values": ["real_cli", "store_direct"]}, - {"name": "ROW_STATUSES", "module": "loopx/control_plane/testing/authority_e2e_ladder.py", "container": "tuple", "values": ["pass", "fail", "unverified"]}, - {"name": "STAGES", "module": "loopx/control_plane/testing/authority_e2e_ladder.py", "container": "tuple", "values": ["0", "1", "2a", "2b", "2c1", "2c2"]}, - {"name": "COMMITTED_OBSERVATION_OUTCOMES", "module": "loopx/control_plane/testing/authority_e2e_row_support.py", "container": "frozenset", "values": ["captured", "replayed", "ambiguous_reconciled"]}, - {"name": "DEFAULT_OFF_PARITY_FIELDS", "module": "loopx/control_plane/testing/authority_e2e_row_support.py", "container": "tuple", "values": ["ok", "added", "already_exists", "metadata_updated", "status_changed", "role", "status", "task_class", "action_kind", "continuation_policy"]}, - {"name": "ARCHIVE_PARITY_REQUIRED_WRITE_CLASSES", "module": "loopx/control_plane/testing/authority_e2e_rows_stage2c2.py", "container": "tuple", "values": ["todo_add", "todo_complete", "todo_archive_completed", "task_lease_acquire", "task_lease_fence_close"]}, - {"name": "DRAIN_CRASH_WINDOWS", "module": "loopx/control_plane/testing/authority_e2e_rows_stage2c2.py", "container": "tuple", "values": ["before_commit", "after_commit", "after_cursor", "between_unlinks"]}, - {"name": "PARITY_REQUIRED_WRITE_CLASSES", "module": "loopx/control_plane/testing/authority_e2e_rows_stage2c2.py", "container": "tuple", "values": ["todo_add", "todo_update", "todo_complete", "todo_supersede", "todo_capture_followups", "task_lease_acquire", "task_lease_renew", "task_lease_transfer", "task_lease_fence_close"]}, - {"name": "MODEL_BEHAVIOR_SEMANTIC_CONTRACT_FIELDS", "module": "loopx/control_plane/testing/model_behavior_qualification.py", "container": "tuple", "values": ["concrete_user_question", "required_reads", "gate_or_stop", "peer_route", "write_scope", "spend_rule", "scheduler_action", "vision_continuation", "planning_horizon", "actionable_warnings"]}, - {"name": "ONBOARDING_MODEL_BEHAVIOR_PHASES", "module": "loopx/control_plane/testing/onboarding_model_behavior_qualification.py", "container": "tuple", "values": ["entry", "postcondition"]}, - {"name": "ONBOARDING_REQUIRED_CONNECT_COMMAND_IDS", "module": "loopx/control_plane/testing/onboarding_model_behavior_qualification.py", "container": "tuple", "values": ["goal_start_connect_if_needed", "goal_start_refresh_state", "goal_start_host_loop_activation", "goal_start_quota_should_run"]}, - {"name": "REQUIRED_QUALIFICATION_IDS", "module": "loopx/control_plane/testing/release_commit_qualification.py", "container": "tuple", "values": ["pytest", "ruff", "mypy", "risk_canary", "full_public", "install_upgrade_host", "public_boundary", "doubao_actual_default"]}, - {"name": "TODO_ACTION_KIND_ADVANCEMENT_VALUES", "module": "loopx/control_plane/todos/contract.py", "container": "set", "values": ["advance", "analyze", "benchmark_run", "codex_run", "compact_blocker_writeback", "compare", "execute", "fix", "implement", "rebuild", "rebuild_score", "repair", "run", "run_eval", "test", "validate", "writeback"]}, - {"name": "TODO_ACTION_KIND_MONITOR_VALUES", "module": "loopx/control_plane/todos/contract.py", "container": "set", "values": ["external_evidence", "monitor", "observe", "poll", "watch"]}, - {"name": "TODO_DECISION_OUTCOME_VALUES", "module": "loopx/control_plane/todos/contract.py", "container": "set", "values": ["approve", "reject", "cancel"]}, - {"name": "TODO_DECISION_SCOPE_GRANULARITY_VALUES", "module": "loopx/control_plane/todos/contract.py", "container": "set", "values": ["action", "lane", "goal", "project", "global"]}, - {"name": "TODO_DECISION_SCOPE_KIND_VALUES", "module": "loopx/control_plane/todos/contract.py", "container": "set", "values": ["private_read", "write_scope", "resource", "production", "public_claim", "direction", "other"]}, - {"name": "TODO_LEGACY_TERMINAL_STATUS_VALUES", "module": "loopx/control_plane/todos/contract.py", "container": "set", "values": ["completed", "closed", "archived"]}, - {"name": "TODO_MONITOR_METADATA_FIELDS", "module": "loopx/control_plane/todos/contract.py", "container": "tuple", "values": ["target_key", "monitor_effect_id", "cadence", "next_due_at", "expires_at", "last_checked_at", "result_hash", "consecutive_no_change", "material_change", "material_change_generation", "max_no_change_before_replan", "watch_only"]}, - {"name": "TODO_REMOVED_REVIEW_CONTINUATION_POLICY_VALUES", "module": "loopx/control_plane/todos/contract.py", "container": "set", "values": ["primary_review", "review_handoff"]}, - {"name": "TODO_GATE_BLOCKING_STATES", "module": "loopx/control_plane/todos/decision_scope.py", "container": "frozenset", "values": ["gate_targets_todo", "gate_covers_action", "projection_repair_required"]}, - {"name": "TODO_FRONTIER_DEADLINE_LANES", "module": "loopx/control_plane/todos/frontier_deadline.py", "container": "tuple", "values": ["current_agent_claimed_monitor_items", "monitor_open_items", "gate_open_items", "deferred_resume_candidates", "current_agent_deferred_resume_candidates", "resume_blocked_items", "current_agent_monitor_blocked_resume_candidates", "current_agent_handoff_gates"]}, - {"name": "FRONTIER_REVISION_FIELDS", "module": "loopx/control_plane/todos/frontier_revision.py", "container": "tuple", "values": ["todo_id", "status", "done", "title", "text", "task_class", "claimed_by", "bound_agent", "blocks_agent", "excluded_agents", "priority", "action_kind", "task_domain", "task_repository", "capability_binding_ref", "required_capabilities", "target_capabilities", "target_key", "continuation_policy", "removed_continuation_policy", "decision_scope", "required_decision_scopes", "decision_outcome", "replan_obligation_id", "unblocks_todo_id", "depends_on_todo_id", "depends_on_todo_ids", "resume_when", "no_followup", "successor_todo_ids", "completion_continuation"]}, - {"name": "THIN_TODO_LIST_ITEM_FIELDS", "module": "loopx/control_plane/todos/list_projection.py", "container": "tuple", "values": ["todo_id", "role", "status", "priority", "text", "task_class", "action_kind", "claimed_by", "bound_agent", "goal_bound", "blocks_agent", "global_gate", "unblocks_todo_id", "decision_scope", "required_decision_scopes", "resume_when", "resume_ready", "target_key", "cadence", "next_due_at", "expires_at", "watch_only"]}, - {"name": "TODO_LIFECYCLE_AUTHORITY_ACTIONS", "module": "loopx/control_plane/todos/mutation_authority.py", "container": "frozenset", "values": ["complete", "reassign", "supersede", "update"]}, - {"name": "AGENT_LANE_STATUS_TODO_ITEM_FIELDS", "module": "loopx/control_plane/todos/quota_summary.py", "container": "tuple", "values": ["schema_version", "index", "todo_id", "text", "title", "status", "priority", "task_class", "action_kind", "task_domain", "claimed_by", "bound_agent", "blocks_agent", "global_gate", "unblocks_todo_id", "required_capabilities", "required_write_scopes", "missing_capabilities", "resume_ready", "next_due_at"]}, - {"name": "QUOTA_PAYLOAD_ITEM_FIELDS", "module": "loopx/control_plane/todos/quota_summary.py", "container": "tuple", "values": ["schema_version", "index", "text", "title", "todo_id", "status", "priority", "task_class", "action_kind", "task_domain", "decision_scope", "required_decision_scopes", "task_repository", "continuation_policy", "required_capabilities", "required_write_scopes", "missing_capabilities", "claimed_by", "bound_agent", "goal_bound", "blocks_agent", "excluded_agents", "global_gate", "unblocks_todo_id", "resume_when", "resume_monitor_generation", "resume_condition", "resume_ready", "blocking_monitor_todo_id", "no_followup", "successor_todo_ids", "completion_continuation", "completion_recovery", "replan_obligation_id", "target_key", "cadence", "next_due_at", "expires_at", "watch_only", "last_checked_at", "result_hash", "consecutive_no_change", "material_change", "material_change_generation", "max_no_change_before_replan", "route_continuation_replan_required", "route_continuation_reason", "route_id", "route_key", "completed_at", "updated_at", "gate_state", "reason"]}, - {"name": "TODO_PLANNING_SOURCE_KEYS", "module": "loopx/control_plane/todos/summary_item.py", "container": "tuple", "values": ["items", "deferred_items", "blocker_items", "monitor_open_items"]}, - {"name": "TODO_SUMMARY_COMPACT_FIELDS", "module": "loopx/control_plane/todos/summary_item.py", "container": "tuple", "values": ["schema_version", "todo_id", "role", "status", "priority", "title", "archive_state", "source_section", "task_class", "action_kind", "task_domain", "capability_binding_ref", "task_repository", "continuation_policy", "removed_continuation_policy", "required_write_scopes", "required_capabilities", "target_capabilities", "decision_scope", "required_decision_scopes", "decision_outcome", "decision_scope_outcomes", "claimed_by", "bound_agent", "goal_bound", "blocks_agent", "excluded_agents", "global_gate", "unblocks_todo_id", "resume_when", "resume_monitor_generation", "resume_condition", "resume_ready", "no_followup", "successor_todo_ids", "completion_continuation", "completion_recovery", "replan_obligation_id", "target_key", "cadence", "next_due_at", "expires_at", "watch_only", "last_checked_at", "result_hash", "consecutive_no_change", "material_change", "material_change_generation", "max_no_change_before_replan", "route_continuation_replan_required", "route_continuation_reason", "route_id", "route_key", "completed_at", "updated_at", "completion_turn_key", "superseded_by", "handoff_note"]}, - {"name": "TODO_SUMMARY_SOURCE_KEYS", "module": "loopx/control_plane/todos/summary_item.py", "container": "tuple", "values": ["active_next_action_items", "active_next_action_executable_items", "first_open_items", "backlog_items", "unclaimed_priority_open_items", "claimed_open_items", "claimed_advancement_open_items", "claimed_monitor_open_items", "monitor_open_items", "current_agent_claimed_open_items", "current_agent_claimed_advancement_items", "current_agent_claimed_monitor_items", "blocker_items", "resume_blocked_items", "monitor_blocked_resume_candidates", "current_agent_monitor_blocked_resume_candidates", "unclaimed_monitor_blocked_resume_candidates", "items"]}, - {"name": "TASK_ORCHESTRATION_CANDIDATE_FIELDS", "module": "loopx/control_plane/todos/todo_summary.py", "container": "tuple", "values": ["todo_id", "status", "done", "task_class", "action_kind", "task_domain", "task_repository", "required_write_scopes", "required_capabilities", "claimed_by", "excluded_agents", "resume_when", "resume_ready", "continuation_policy", "target_key", "completion_validation_required", "title", "text"]}, - {"name": "TASK_ORCHESTRATION_USER_BLOCKER_FIELDS", "module": "loopx/control_plane/todos/todo_summary.py", "container": "tuple", "values": ["todo_id", "status", "done", "task_class", "unblocks_todo_id"]}, - {"name": "USER_GATE_ACTION_KIND_HINTS", "module": "loopx/control_plane/todos/user_gate.py", "container": "tuple", "values": ["approval", "approve", "boundary", "gate", "blocker", "credential", "private", "production", "leaderboard", "submission", "public_claim"]}, - {"name": "CODEX_CLI_RESULT_KINDS", "module": "loopx/control_plane/turn_driver/codex_cli.py", "container": "tuple", "values": ["validated_progress", "repair_required", "replan_required", "user_action_required", "wait", "iteration_failed"]}, - {"name": "CODEX_CLI_SANDBOXES", "module": "loopx/control_plane/turn_driver/codex_cli.py", "container": "tuple", "values": ["read-only", "workspace-write"]}, - {"name": "SESSION_INVALIDATING_FAILURE_CATEGORIES", "module": "loopx/control_plane/turn_driver/codex_cli.py", "container": "frozenset", "values": ["model_requires_newer_codex", "output_schema_rejected", "session_missing"]}, - {"name": "DELIVERY_CONTINUITY_DECISIONS", "module": "loopx/control_plane/turn_driver/delivery_continuity.py", "container": "set", "values": ["resume_in_flight", "release_for_reselection", "preempt"]}, - {"name": "DELIVERY_CONTINUITY_PREEMPTIONS", "module": "loopx/control_plane/turn_driver/delivery_continuity.py", "container": "set", "values": ["heartbeat_receipt", "blocking_work_lane", "autonomous_replan", "control_repair", "delivery_not_allowed"]}, - {"name": "DELIVERY_ROUTING_SELECTIONS", "module": "loopx/control_plane/turn_driver/delivery_continuity.py", "container": "set", "values": ["continuity", "fallback", "none"]}, - {"name": "REPAIR_ACTIONS", "module": "loopx/control_plane/turn_driver/driver.py", "container": "set", "values": ["capability_repair", "projection_repair", "self_repair", "state_projection_repair", "workspace_repair"]}, - {"name": "REPLAN_ACTIONS", "module": "loopx/control_plane/turn_driver/driver.py", "container": "set", "values": ["autonomous_replan", "autonomous_replan_required", "successor_replan_required"]}, - {"name": "SUPPORTED_EXECUTION_MODES", "module": "loopx/control_plane/turn_driver/driver.py", "container": "set", "values": ["interactive-visible", "isolated-headless"]}, - {"name": "SUPPORTED_HOSTS", "module": "loopx/control_plane/turn_driver/driver.py", "container": "set", "values": ["codex-cli", "claude-code", "dsh", "generic-cli"]}, - {"name": "SUPPORTED_ITERATION_CONTEXT_POLICIES", "module": "loopx/control_plane/turn_driver/driver.py", "container": "set", "values": ["fresh", "resume_if_available"]}, - {"name": "REASONING_EFFORTS", "module": "loopx/control_plane/turn_driver/execution_profile.py", "container": "tuple", "values": ["none", "minimal", "low", "medium", "high", "xhigh", "max", "ultra"]}, - {"name": "HOST_PATH_DELTA_MODES", "module": "loopx/control_plane/turn_driver/executor.py", "container": "set", "values": ["", "unchanged", "material_replan"]}, - {"name": "HOST_RESULT_FIELDS", "module": "loopx/control_plane/turn_driver/executor.py", "container": "set", "values": ["schema_version", "turn_key", "result_kind", "completed_phases", "classification", "recommended_action", "next_action", "delivery_batch_scale", "delivery_outcome", "vision_unchanged_reason", "path_delta_mode", "agent_vision_json", "summary", "reward_memory_reflection_json"]}, - {"name": "HOST_FAILURE_KINDS", "module": "loopx/control_plane/turn_driver/host_failure.py", "container": "frozenset", "values": ["auth_failed", "contract_rejected", "executor_timeout", "provider_capacity", "provider_overloaded", "quota_exhausted", "rate_limited", "session_missing", "transport_lost", "unknown"]}, - {"name": "TURN_LANE_HOLDER_TEXT_FIELDS", "module": "loopx/control_plane/turn_driver/lane_fence.py", "container": "tuple", "values": ["agent_id", "operation", "acquired_at"]}, - {"name": "CHILD_CONTEXT_MODES", "module": "loopx/control_plane/turn_driver/subagent_execution_topology.py", "container": "frozenset", "values": ["fresh", "forked_snapshot", "resume"]}, - {"name": "CHILD_FALLBACK_ACTIONS", "module": "loopx/control_plane/turn_driver/subagent_execution_topology.py", "container": "tuple", "values": ["retry_fresh", "replace_child", "serial_takeover", "ignore_optional_result"]}, - {"name": "KNOWN_EFFECT_CLASSES", "module": "loopx/control_plane/turn_driver/subagent_execution_topology.py", "container": "frozenset", "values": ["local_read", "network_read", "held_workspace_write", "external_write", "production_action", "credential_use", "monitor"]}, - {"name": "RECEIPT_STATUSES", "module": "loopx/control_plane/turn_driver/subagent_execution_topology.py", "container": "frozenset", "values": ["running", "completed", "failed", "cancelled", "rejected"]}, - {"name": "ISSUE_META_SURFACE_SECTION_HEADINGS", "module": "loopx/control_plane/work_items/issue_meta_surface.py", "container": "tuple", "values": ["Issue Meta Surface", "Issue/PR Meta Surface"]}, - {"name": "CAPTURE_SCOPES", "module": "loopx/control_plane/work_items/operator_inbox.py", "container": "set", "values": ["addressed_only", "configured_chat_all"]}, - {"name": "FRESH_VISION_PATH_DISPOSITIONS", "module": "loopx/control_plane/work_items/progress_observation.py", "container": "frozenset", "values": ["continue", "no_change", "replan"]}, - {"name": "REPLAN_REQUIRED_OUTCOMES", "module": "loopx/control_plane/work_items/progress_observation.py", "container": "tuple", "values": ["new_surface", "new_hypothesis", "new_probe_family", "new_runnable_successor", "coverage_backed_exploration_exhausted", "new_concrete_blocker", "coverage_backed_no_followup"]}, - {"name": "SEMANTIC_DIMENSIONS", "module": "loopx/control_plane/work_items/progress_observation.py", "container": "tuple", "values": ["surface_id", "hypothesis_id", "probe_kind"]}, - {"name": "VISION_REPLAN_REQUIRED_OUTCOMES", "module": "loopx/control_plane/work_items/progress_observation.py", "container": "tuple", "values": ["fresh_vision_path_outcome", "new_runnable_successor", "new_concrete_blocker", "coverage_backed_exploration_exhausted", "coverage_backed_no_followup"]}, - {"name": "VISION_REPLAN_TRIGGER_KINDS", "module": "loopx/control_plane/work_items/progress_observation.py", "container": "frozenset", "values": ["vision_acceptance_gap", "vision_checkpoint_missing", "vision_outcome_checkpoint_required", "vision_successor_required", "required_agent_vision_missing"]}, - {"name": "PROJECT_ASSET_HANDOFF_STATE_TRACE_CHECK_KEYS", "module": "loopx/control_plane/work_items/project_asset.py", "container": "tuple", "values": ["project_asset_backed", "same_source_should_run", "handoff_has_next_action", "handoff_has_stop_condition", "handoff_sanitized_surface"]}, - {"name": "PROJECT_ASSET_TODO_DISPLAY_FIELDS", "module": "loopx/control_plane/work_items/project_asset.py", "container": "tuple", "values": ["index", "done", "schema_version", "todo_id", "role", "status", "priority", "archive_state", "source_section", "task_class", "action_kind", "task_domain", "task_repository", "required_write_scopes", "required_capabilities", "target_capabilities", "decision_scope", "required_decision_scopes", "claimed_by", "bound_agent", "goal_bound", "blocks_agent", "excluded_agents", "global_gate", "unblocks_todo_id", "resume_when", "resume_monitor_generation", "resume_condition", "resume_ready", "blocking_monitor_todo_id", "no_followup", "successor_todo_ids", "target_key", "cadence", "next_due_at", "expires_at", "last_checked_at", "result_hash", "consecutive_no_change", "material_change", "material_change_generation", "max_no_change_before_replan", "route_continuation_replan_required", "route_continuation_reason", "route_id", "route_key", "completed_at", "updated_at", "superseded_by"]}, - {"name": "ACCOUNTABLE_REPLAN_DELTA_KINDS", "module": "loopx/control_plane/work_items/repair_delta.py", "container": "frozenset", "values": ["blocker", "capability_gate", "goal_boundary_projection", "goal_vision_patch", "no_followup", "runnable_todo_set", "successor_or_supersede", "exploration_exhausted"]}, - {"name": "FRONTIER_REPLAN_ACK_DELTA_KINDS", "module": "loopx/control_plane/work_items/repair_delta.py", "container": "frozenset", "values": ["active_state_next_action", "blocker", "goal_vision_patch", "no_followup", "runnable_todo_set", "successor_or_supersede", "watch_lane_continuation", "exploration_exhausted"]}, - {"name": "REPAIR_DELTA_KIND_CHOICES", "module": "loopx/control_plane/work_items/repair_delta.py", "container": "tuple", "values": ["effective_action", "interaction_contract", "runnable_todo_set", "user_gate", "blocker", "successor_or_supersede", "capability_gate", "monitor_target", "active_state_next_action", "goal_vision_patch", "goal_boundary_projection", "no_followup", "watch_lane_continuation", "exploration_exhausted"]}, - {"name": "TASK_GRAPH_AUDIT_MARKERS", "module": "loopx/control_plane/work_items/task_graph.py", "container": "tuple", "values": ["audit", "audited"]}, - {"name": "TASK_GRAPH_CONTINUATION_MARKERS", "module": "loopx/control_plane/work_items/task_graph.py", "container": "tuple", "values": ["continuation", "continue", "continuing", "continued"]}, - {"name": "TASK_GRAPH_SOURCE_OF_TRUTH", "module": "loopx/control_plane/work_items/task_graph.py", "container": "list", "values": ["event_ledger", "active_goal_state", "todos", "gates", "leases", "run_history"]}, - {"name": "PRIVATE_BOUNDARY_MONITOR_ACTION_KIND_HINTS", "module": "loopx/control_plane/work_items/work_lane.py", "container": "tuple", "values": ["private", "local_department_doc"]}, - {"name": "WORK_LANE_TODO_ITEM_FIELDS", "module": "loopx/control_plane/work_items/work_lane.py", "container": "tuple", "values": ["index", "text", "todo_id", "status", "priority", "task_class", "action_kind", "task_repository", "claimed_by", "target_key", "next_due_at", "expires_at", "resume_when", "resume_ready", "blocking_monitor_todo_id", "result_hash"]}, - {"name": "DEPENDENCY_OBSERVATION_CLASSIFICATION_HINTS", "module": "loopx/control_plane/work_items/work_lane_context.py", "container": "tuple", "values": ["dependency_observed", "dependency_observation", "dependency_monitor"]}, - {"name": "AGENT_REASONING_CHECKLIST", "module": "loopx/diagnose.py", "container": "list", "values": ["判断 LoopX 本身是否健康:registry/status/quota 是否能读,是否存在 stale projection 或 contract error。", "判断当前 goal 是否已连接:若没有 registry goal 或 attention item,先接入而不是猜任务。", "判断是否存在 user/controller gate:开放 user todo、operator_question、interaction_contract.user_channel 都要纳入判断。", "判断是否可自主推进:只有在用户 gate 不阻塞所选路径、quota 允许、goal_boundary 允许、且有明确 agent todo/recommended_action 时才推进。", "判断是否应先自修复:state_projection_gap、boundary_projection_gap、stale action、todo 投影缺口优先于普通 delivery。", "向用户汇报时给出自己的结论,不要把 machine_signals 当成最终裁决;引用具体 evidence 字段说明理由。"]}, - {"name": "GUARDRAIL_STATUSES", "module": "loopx/domain_packs/ml_experiment.py", "container": "tuple", "values": ["clean", "warning", "failed", "unknown"]}, - {"name": "HYPOTHESIS_STATUSES", "module": "loopx/domain_packs/ml_experiment.py", "container": "tuple", "values": ["active", "supported", "weakened", "retired", "unknown"]}, - {"name": "VOLC_MLP_TASK_STATES", "module": "loopx/domain_packs/ml_experiment.py", "container": "tuple", "values": ["Creating", "Waiting", "Queueing", "Deploying", "Running", "Stopping", "Completed", "Failed", "Stopped", "Unknown"]}, - {"name": "DREAMING_PROPOSAL_DECISIONS", "module": "loopx/dreaming.py", "container": "set", "values": ["approve", "defer", "reject"]}, - {"name": "ACCEPTED_RESULT_KINDS", "module": "loopx/dsh_goal_mode/turn_host_adapter.py", "container": "set", "values": ["validated_progress", "repair_required", "replan_required", "user_action_required", "wait", "iteration_failed"]}, - {"name": "COMPLETED_PHASES", "module": "loopx/dsh_goal_mode/turn_host_adapter.py", "container": "list", "values": ["host_execute", "typed_result"]}, - {"name": "MATERIAL_KINDS", "module": "loopx/dsh_goal_mode/turn_host_adapter.py", "container": "set", "values": ["validated_progress", "repair_required", "replan_required"]}, - {"name": "PLANNER_WORKER_ACTION_KINDS", "module": "loopx/experiments/planner_worker/contract.py", "container": "frozenset", "values": ["edit", "create", "delete", "validate", "research", "design", "investigate"]}, - {"name": "PLANNER_WORKER_AUTONOMY", "module": "loopx/experiments/planner_worker/contract.py", "container": "frozenset", "values": ["narrow", "bounded", "open"]}, - {"name": "PLANNER_WORKER_EXECUTORS", "module": "loopx/experiments/planner_worker/contract.py", "container": "frozenset", "values": ["cheap_worker", "strong_worker", "planner_only"]}, - {"name": "PLANNER_WORKER_MODEL_TIERS", "module": "loopx/experiments/planner_worker/contract.py", "container": "frozenset", "values": ["cheap", "strong", "none"]}, - {"name": "DEFAULT_VALIDATION_EXECUTABLES", "module": "loopx/experiments/planner_worker/traex.py", "container": "frozenset", "values": ["python", "python3", "pytest", "go", "npm", "npx", "cargo"]}, - {"name": "TERMINAL_SETUP_STATES", "module": "loopx/extensions/lark/app_setup.py", "container": "set", "values": ["ready", "failed", "cancelled"]}, - {"name": "CORE_BOT_SCOPES", "module": "loopx/extensions/lark/bot_scopes.py", "container": "tuple", "values": ["im:message", "im:chat:read", "im:chat:create", "im:chat:update", "im:chat.members:read", "im:chat.members:write_only", "contact:user.base:readonly", "contact:contact.base:readonly", "application:application:self_manage", "application:bot.basic_info:read"]}, - {"name": "INBOX_BOT_SCOPES", "module": "loopx/extensions/lark/bot_scopes.py", "container": "tuple", "values": ["im:message:readonly", "im:message.group_msg", "im:message.group_msg.include_bot:read", "im:message.p2p_msg:readonly"]}, - {"name": "SINK_BOT_SCOPES", "module": "loopx/extensions/lark/bot_scopes.py", "container": "tuple", "values": ["cardkit:card:read", "cardkit:card:write", "docs:document.comment:read", "docs:document.comment:create", "docs:document.comment:delete"]}, - {"name": "SUPPORTED_SUPERVISORS", "module": "loopx/extensions/lark/event_collector.py", "container": "set", "values": ["launchd", "systemd"]}, - {"name": "ADDRESSING_SOURCES", "module": "loopx/extensions/lark/event_inbox.py", "container": "set", "values": ["provider_mention", "verified_reply", "legacy_text"]}, - {"name": "CAPTURE_SCOPES", "module": "loopx/extensions/lark/event_inbox.py", "container": "set", "values": ["addressed_only", "configured_chat_all"]}, - {"name": "REPLY_EDITORIAL_STYLES", "module": "loopx/extensions/lark/event_inbox.py", "container": "set", "values": ["concise", "bullet_points_preferred"]}, - {"name": "REPLY_PLACEMENT_POLICIES", "module": "loopx/extensions/lark/event_inbox.py", "container": "set", "values": ["source_thread", "source_context"]}, - {"name": "PRIVATE_PACKET_KEYS", "module": "loopx/extensions/lark/goal_channel_contracts.py", "container": "set", "values": ["base_token", "chat_id", "config_path", "message_id", "path", "profile", "sender_profile", "table_id"]}, - {"name": "REQUIRED_BOT_GROUP_HISTORY_SCOPES", "module": "loopx/extensions/lark/goal_channel_transport.py", "container": "tuple", "values": ["im:message.group_msg", "im:message.group_msg.include_bot:read"]}, - {"name": "REQUIRED_GOAL_TOPIC_SCOPES", "module": "loopx/extensions/lark/goal_channel_transport.py", "container": "tuple", "values": ["im:message", "im:message:readonly"]}, - {"name": "INCOMING_MODES", "module": "loopx/extensions/lark/goal_topic_connections.py", "container": "set", "values": ["mentions", "all"]}, - {"name": "REACTION_PHASES", "module": "loopx/extensions/lark/inbox_reactions.py", "container": "set", "values": ["received", "processing"]}, - {"name": "RECEIVED_OPERATION_PHASES", "module": "loopx/extensions/lark/inbox_reactions.py", "container": "set", "values": ["prepared", "created"]}, - {"name": "MENTION_ID_KEYS", "module": "loopx/extensions/lark/outbound.py", "container": "tuple", "values": ["open_id", "user_id", "union_id"]}, - {"name": "ISSUE_FIX_CARD_FIELDS", "module": "loopx/extensions/lark/presentation/issue_fix_surface.py", "container": "list", "values": ["Task", "Repository", "Issue", "Pull Request", "Route", "Stage", "Validation", "Outcome", "Context Tags", "Status"]}, - {"name": "ISSUE_FIX_METRIC_FIELDS", "module": "loopx/extensions/lark/presentation/issue_fix_surface.py", "container": "list", "values": ["Task", "Metric Group", "Metric", "Baseline", "Current", "Delta", "Numerator", "Denominator", "Metric Source", "Metric Updated At", "Missing Data"]}, - {"name": "ISSUE_FIX_STAGE_OPTIONS", "module": "loopx/extensions/lark/presentation/issue_fix_surface.py", "container": "list", "values": ["reproduction_planned", "fix_in_progress", "fix_review_ready", "ci_pending", "ci_failed", "review_wait", "changes_requested", "merge_ready", "reproduction_blocked", "fix_blocked", "delivery_blocked", "draft_pr", "branch_stale_or_conflicted", "pr_open", "merged", "closed_without_merge", "comment_packet", "comment_blocked", "comment_published", "triage_complete"]}, - {"name": "OPERATOR_CARD_FIELDS", "module": "loopx/extensions/lark/presentation/kanban.py", "container": "list", "values": ["Task", "Claim", "Priority", "User Gate", "Evidence", "Status"]}, - {"name": "HUMAN_REWARD_FIELDS", "module": "loopx/feedback.py", "container": "tuple", "values": ["recorded_at", "decision", "reward", "reason_summary", "follow_up", "lesson"]}, - {"name": "LESSON_KINDS", "module": "loopx/feedback.py", "container": "set", "values": ["route", "priority", "benchmark_protocol", "safety_boundary", "operating_rule"]}, - {"name": "REWARD_VALUES", "module": "loopx/feedback.py", "container": "set", "values": ["positive", "negative", "mixed", "neutral"]}, - {"name": "RUN_OVERLAY_FIELDS", "module": "loopx/feedback.py", "container": "tuple", "values": ["generated_at", "goal_id", "classification", "recommended_action", "health_check", "active_task_count", "active_priorities", "cache_check", "controller_readiness", "json_path", "markdown_path"]}, - {"name": "ATTENTION_OVERRIDE_FIELDS", "module": "loopx/global_registry.py", "container": "tuple", "values": ["waiting_on", "attention_status", "operator_question", "recommended_action", "next_handoff_condition"]}, - {"name": "ROUTE_FIELDS", "module": "loopx/global_registry.py", "container": "tuple", "values": ["source_registry", "repo", "state_file"]}, - {"name": "BOUNDARY_CODES", "module": "loopx/global_risks.py", "container": "set", "values": ["public_boundary_violation", "registry_boundary_risk"]}, - {"name": "SOURCE_SURFACES", "module": "loopx/global_risks.py", "container": "list", "values": ["status contract diagnostics", "global registry health findings", "status attention queue stale-run warnings", "status compact run-history coordination"]}, - {"name": "SOURCE_SURFACES", "module": "loopx/global_todos.py", "container": "list", "values": ["status attention queue", "quota should-run summaries", "active-state todo projections", "compact run-history projection consumed by status"]}, - {"name": "REGISTRY_DIRS", "module": "loopx/goal_mode_context.py", "container": "tuple", "values": [".loopx", ".goal-harness"]}, - {"name": "HELP_FLAGS", "module": "loopx/help_surface.py", "container": "set", "values": ["-h", "--help"]}, - {"name": "MANPAGE_COMMAND_HELP_ONLY", "module": "loopx/help_surface.py", "container": "frozenset", "values": ["agent-context", "archive-runtime", "automation-prompts", "authority-shadow", "backup-state", "capability", "chat-endpoint", "codex-cli-bounded-visible-pilot-adapter", "codex-cli-exec-handoff", "codex-cli-local-driver-plan", "codex-cli-local-scheduler-exec", "codex-cli-local-scheduler-tick", "codex-cli-one-message-loop-pilot", "codex-cli-runtime-idle-detector", "codex-cli-session-probe", "codex-cli-visible-driver-plan", "codex-cli-visible-driver-run", "codex-cli-visible-first-response-capture-plan", "codex-cli-visible-local-driver-pilot", "codex-cli-visible-session-proof", "configure-goal", "content-ops", "decision-context", "dash", "material-lifecycle", "demo", "dreaming", "global-gates", "global-risks", "global-summary", "global-todos", "goal-actions", "goal-alignment", "amendment-proposal", "goal-amendment-proposal", "handoff-mode", "heartbeat-prequota", "import-doc-registry-authority", "lark-inbox", "migrate-state", "ml-experiment", "opencode2-goal-worker", "operator-gate", "pr-review", "promotion-gate", "read-only-map", "refresh-state", "reliability-diagnostics", "register-authority-source", "registry-boundary", "reward", "reward-memory", "semantic-preference", "serve-status", "shared-goal-alignment", "uninstall-project", "value-connectors", "version", "worker-bridge"]}, - {"name": "REGISTRY_ATTENTION_FIELDS", "module": "loopx/history.py", "container": "tuple", "values": ["waiting_on", "attention_status", "operator_question", "recommended_action", "next_handoff_condition"]}, - {"name": "HOST_MANAGED_SKILL_AGENT_TYPES", "module": "loopx/host_loop_activation.py", "container": "frozenset", "values": ["ark-managed-agent", "deepseek-harness-native", "trae_app", "traex-cli", "other-agent"]}, - {"name": "SUPPORTED_AGENT_TYPES", "module": "loopx/host_loop_activation.py", "container": "list", "values": ["ark-managed-agent", "codex-app", "codex-app-ssh", "codex-ide-plugin", "codex-cli", "trae_app", "claude-code", "kunluncode", "opencode", "opencode2", "traex-cli", "pi", "gemini-cli", "cursor-agent", "zcode", "agy", "kiro-cli", "deepseek-harness", "deepseek-harness-native", "manual", "other-agent"]}, - {"name": "KIRO_CLI_ACCEPTED_INPUTS", "module": "loopx/kiro_cli_goal_mode/__init__.py", "container": "tuple", "values": ["kiro-cli", "kiro_cli", "kiro cli", "kirocli", "kiro", "kiro-cli-tui", "kiro tui"]}, - {"name": "KIRO_CLI_HOOK_TRIGGERS", "module": "loopx/kiro_cli_goal_mode/__init__.py", "container": "tuple", "values": ["agentSpawn", "userPromptSubmit", "preToolUse", "postToolUse", "stop"]}, - {"name": "TERMINAL_GOAL_STATUSES", "module": "loopx/kunluncode_goal_mode/app_server.py", "container": "set", "values": ["blocked", "budget_limited", "complete", "paused", "usage_limited"]}, - {"name": "AVAILABLE_CAPABILITIES", "module": "loopx/kunluncode_goal_mode/control_plane.py", "container": "tuple", "values": ["shell", "filesystem_write"]}, - {"name": "BLOCKED_QUOTA_STATES", "module": "loopx/long_task_cadence.py", "container": "frozenset", "values": ["blocked", "blocked_health", "focus_wait", "monitor_only", "operator_gate", "paused", "throttled", "user_gate", "waiting"]}, - {"name": "MATERIAL_PROGRESS_GRANULARITIES", "module": "loopx/long_task_cadence.py", "container": "frozenset", "values": ["multi_surface", "implementation_plus_validation", "milestone"]}, - {"name": "SMALL_PROGRESS_GRANULARITIES", "module": "loopx/long_task_cadence.py", "container": "frozenset", "values": ["status_only", "single_surface"]}, - {"name": "MARKDOWN_SUFFIXES", "module": "loopx/materials.py", "container": "set", "values": [".md", ".markdown"]}, - {"name": "OPERATOR_GATE_DECISIONS", "module": "loopx/operator_gate.py", "container": "set", "values": ["approve", "reject", "defer"]}, - {"name": "EXPLORE_HARNESS_PROFILES", "module": "loopx/orchestration.py", "container": "tuple", "values": ["generic", "adaptive-resilient", "moe-router"]}, - {"name": "SUBAGENT_REASONING_EFFORTS", "module": "loopx/orchestration.py", "container": "tuple", "values": ["none", "minimal", "low", "medium", "high", "xhigh", "max", "ultra"]}, - {"name": "CODE_EXTENSIONS", "module": "loopx/pr_review.py", "container": "tuple", "values": [".py", ".js", ".jsx", ".ts", ".tsx", ".go", ".rs", ".java", ".kt", ".kts", ".c", ".cc", ".cpp", ".h", ".hpp", ".cs", ".rb", ".php", ".swift", ".m", ".mm"]}, - {"name": "REQUIRED_REVIEW_SECTION_HEADINGS", "module": "loopx/pr_review.py", "container": "tuple", "values": ["动机", "改动思路", "具体改动", "对主干的风险", "我的整体评价"]}, - {"name": "RUNTIME_OR_CLI_PREFIXES", "module": "loopx/pr_review.py", "container": "tuple", "values": ["src/", "lib/", "pkg/", "packages/", "cmd/", "internal/", "server/", "backend/", "app/", "apps/", "scripts/", "bin/", "tools/"]}, - {"name": "SOURCE_SURFACES", "module": "loopx/pr_review.py", "container": "list", "values": ["GitHub pull request metadata", "GitHub pull request body summary", "GitHub pull request changed-file list", "GitHub pull request status check rollup"]}, - {"name": "UI_PREFIXES", "module": "loopx/pr_review.py", "container": "tuple", "values": ["apps/presentation/dashboard/", "apps/web/", "apps/frontend/", "apps/site/", "web/", "frontend/", "ui/", "components/", "pages/", "views/", "public/"]}, - {"name": "PUBLIC_SAFE_BOUNDARY_FIELDS", "module": "loopx/presentation/public_safety.py", "container": "tuple", "values": ["raw_logs_recorded", "raw_transcripts_recorded", "raw_connector_payloads_recorded", "credential_values_recorded", "absolute_paths_recorded", "private_source_bodies_recorded"]}, - {"name": "PROJECT_INVENTORY_PATHS", "module": "loopx/project_map.py", "container": "tuple", "values": ["README.md", "AGENTS.md", ".loopx/registry.json", ".codex/goals", "docs", "tests", "package.json", "pyproject.toml", "requirements.txt"]}, - {"name": "READ_ONLY_MAP_ADAPTER_STATUSES", "module": "loopx/project_map.py", "container": "set", "values": ["connected", "connected-read-only", "read-only-map-ready"]}, - {"name": "STATE_SECTIONS", "module": "loopx/project_map.py", "container": "tuple", "values": ["Authority Sources", "Operating Contract", "Work Clusters", "Validation Surfaces", "Private/Public Boundary", "Next Action", "Progress Ledger"]}, - {"name": "PRIVATE_REGISTRY_PARTS", "module": "loopx/registry.py", "container": "set", "values": [".loopx", ".local", ".codex", "runtime"]}, - {"name": "REPRESENTATIVE_CLI_IMPORTS", "module": "loopx/release_candidate.py", "container": "tuple", "values": ["loopx.cli", "loopx.history", "loopx.quota", "loopx.status"]}, - {"name": "REPRESENTATIVE_DISTRIBUTION_PATHS", "module": "loopx/release_candidate.py", "container": "tuple", "values": ["loopx/cli.py", "loopx/doctor.py", "loopx/history.py", "loopx/quota.py", "loopx/release_candidate.py", "loopx/status.py", "loopx/cli_commands/doctor.py", "loopx/cli_commands/quota.py", "loopx/cli_commands/status.py"]}, - {"name": "PRIVATE_SOURCE_KINDS", "module": "loopx/rollout_event_log.py", "container": "set", "values": ["codex_sessions_jsonl", "local_runtime_state", "private_runner_artifact", "unknown_private_source"]}, - {"name": "RAW_KEY_HINTS", "module": "loopx/rollout_event_log.py", "container": "tuple", "values": ["credential", "local_path", "log", "path", "raw", "secret", "stderr", "stdout", "task_text", "token", "trace", "trajectory", "transcript"]}, - {"name": "ROLLOUT_EVENT_KINDS", "module": "loopx/rollout_event_log.py", "container": "set", "values": ["codex_session_observed", "capability_gap", "compact_blocker", "compact_case_result", "evidence_log_read", "failure_attribution", "pr_merge", "pr_review_ack", "quota_monitor_poll", "quota_should_run", "quota_spend", "quota_void", "refresh_state", "refresh_external_delivery", "research_evidence", "research_hypothesis", "todo_add", "todo_archive_completed", "todo_claim", "todo_complete", "todo_supersede", "todo_update", "validation"]}, - {"name": "SKIP_PARTS", "module": "loopx/semantics/inventory.py", "container": "frozenset", "values": ["__pycache__", "node_modules"]}, - {"name": "SOURCE_SUFFIXES", "module": "loopx/semantics/inventory.py", "container": "tuple", "values": [".py", ".ts"]}, - {"name": "BLOCKED_STATUSES", "module": "loopx/session_runtime.py", "container": "set", "values": ["blocked", "error", "failed", "timed_out"]}, - {"name": "COMPACT_SUFFIX_WORDS", "module": "loopx/session_runtime.py", "container": "frozenset", "values": ["id", "ids", "ref", "refs", "count", "at"]}, - {"name": "EXPLICIT_COMPACT_COLLISION_KEYS", "module": "loopx/session_runtime.py", "container": "frozenset", "values": ["conversation_id", "log_count", "message_id", "prompt_token_count", "prompt_tokens", "trace_id"]}, - {"name": "HUMAN_GATE_ACTORS", "module": "loopx/session_runtime.py", "container": "set", "values": ["controller", "human", "operator", "owner", "user"]}, - {"name": "OPEN_GATE_STATUSES", "module": "loopx/session_runtime.py", "container": "set", "values": ["blocked", "needs_decision", "open", "pending", "requested", "requires_decision", "waiting"]}, - {"name": "SOURCE_ID_KEYS", "module": "loopx/session_runtime.py", "container": "tuple", "values": ["session_id", "event_id", "outcome_id", "gate_id", "approval_id", "artifact_id", "tool_call_id", "run_id", "ref_id"]}, - {"name": "TIMESTAMP_KEYS", "module": "loopx/session_runtime.py", "container": "tuple", "values": ["created_at", "event_at", "updated_at", "timestamp"]}, - {"name": "VALIDATED_STATUSES", "module": "loopx/session_runtime.py", "container": "set", "values": ["ok", "passed", "success", "validated"]}, - {"name": "PACKAGED_HOST_SKILL_IDS", "module": "loopx/skill_install_readback.py", "container": "list", "values": ["loopx-project", "loopx-pr-program", "loopx-pr-review", "loopx-doc-registry", "loopx-benchmark", "loopx-self-repair"]}, - {"name": "EXISTING_LOOPX_CAPABILITY_SKILL_SIGNATURES", "module": "loopx/slash_command_files.py", "container": "tuple", "values": ["# LoopX PR Review", "Run `loopx pr-review` first"]}, - {"name": "LEGACY_UPGRADABLE_SIGNATURES", "module": "loopx/slash_command_files.py", "container": "tuple", "values": ["loopx goal-mode setup (NOT Claude Code's built-in /goal)", "The output is loopx control-plane SETUP", "goalmode_cmd.py"]}, - {"name": "STAT_FIELDS", "module": "loopx/state_backup.py", "container": "tuple", "values": ["paths", "files", "directories", "symlinks", "bytes"]}, - {"name": "AGENT_TODO_HEADER_MARKERS", "module": "loopx/state_projection.py", "container": "tuple", "values": ["agent todo", "agent backlog", "agent action", "项目 agent", "agent 待办"]}, - {"name": "TODO_METADATA_KEYS", "module": "loopx/state_projection.py", "container": "tuple", "values": ["action_kind", "task_domain", "capability_binding_ref", "task_repository", "continuation_policy", "required_write_scopes", "required_capabilities", "target_capabilities", "decision_scope", "required_decision_scopes", "claimed_by", "bound_agent", "goal_bound", "blocks_agent", "excluded_agents", "global_gate", "unblocks_todo_id", "successor_todo_ids", "completion_continuation", "completion_recovery", "resume_when", "no_followup", "target_key", "cadence", "next_due_at", "last_checked_at", "result_hash", "consecutive_no_change", "material_change", "max_no_change_before_replan", "note", "evidence", "reason", "completed_at", "updated_at", "superseded_by"]}, - {"name": "USER_TODO_HEADER_MARKERS", "module": "loopx/state_projection.py", "container": "tuple", "values": ["user todo", "owner review", "owner todo", "user action", "用户", "人工", "owner"]}, - {"name": "AUTONOMOUS_RUN_HISTORY_NEUTRAL_CLASSIFICATIONS", "module": "loopx/status.py", "container": "set", "values": ["quota_slot_spent", "quota_slot_voided", "delivery_completion_spend_accounted_v0"]}, - {"name": "BACKLOG_HYGIENE_SECTION_HEADINGS", "module": "loopx/status.py", "container": "tuple", "values": ["Next Action", "Operating Lessons"]}, - {"name": "CONNECTED_ADAPTER_STATUSES", "module": "loopx/status.py", "container": "set", "values": ["connected", "connected-read-only", "pre-tick-runnable"]}, - {"name": "EVENT_LEDGER_EVIDENCE_CLASSIFICATIONS", "module": "loopx/status.py", "container": "set", "values": ["inspect_eval_result", "inspect_result", "needs_more_read_only_evidence", "read_only_project_map"]}, - {"name": "EVENT_LEDGER_EVIDENCE_HINTS", "module": "loopx/status.py", "container": "tuple", "values": ["artifact", "blocker", "ci", "data", "deploy", "done", "eval", "evidence", "failure", "fail", "metric", "monitor", "validation"]}, - {"name": "EVENT_LEDGER_STATE_CLASSIFICATIONS", "module": "loopx/status.py", "container": "set", "values": ["state_refreshed", "public_harness_healthy"]}, - {"name": "LEGACY_EXTERNAL_EVIDENCE_CLASSIFICATION_PREFIXES", "module": "loopx/status.py", "container": "tuple", "values": ["await_", "external_evidence_observation_"]}, - {"name": "LIFECYCLE_PRIORITY", "module": "loopx/status.py", "container": "tuple", "values": ["controller_ready", "reward_judged", "operator_approved", "controller_gated", "operator_gated", "adapter_inspected", "mapped", "refreshed", "connected", "registered", "planned", "run_recorded"]}, - {"name": "REGISTRY_WAITING_ON_OVERRIDES", "module": "loopx/status.py", "container": "set", "values": ["user_or_controller", "controller", "codex", "external_evidence"]}, - {"name": "SOURCE_REGISTRY_SHADOW_FINDINGS", "module": "loopx/status.py", "container": "set", "values": ["source_registry_missing", "stale_source_registry"]}, - {"name": "CONFIGURE_GOAL_REQUEST_FIELDS", "module": "loopx/status_server.py", "container": "set", "values": ["goal_id", "quota_compute", "quota_window_hours", "self_repair_enabled", "self_repair_health", "self_repair_waiting_projection", "multi_subagent_feature", "orchestration_mode", "spawn_allowed", "max_children", "allowed_domains", "clear_allowed_domains", "registered_agents", "clear_registered_agents", "peer_task_coordinator", "clear_peer_task_coordinator", "agent_profiles", "clear_agent_profiles", "agent_work_modes", "clear_agent_work_modes", "todo_lifecycle_authority", "clear_todo_lifecycle_authority", "agent_model", "supervisor_agent", "supervised_agents", "clear_supervisor", "write_scope", "replace_write_scope", "clear_write_scope", "boundary_authority_scopes", "boundary_authority_source", "boundary_authority_decision_id", "boundary_authority_recorded_at", "boundary_authority_expires_at", "clear_boundary_authority"]}, - {"name": "REWARD_REQUEST_FIELDS", "module": "loopx/status_server.py", "container": "set", "values": ["goal_id", "run_generated_at", "recorded_at", "decision", "reward", "reason_summary", "follow_up", "lesson_kind", "lesson_summary", "lesson_avoid", "lesson_prefer"]}, - {"name": "GLOBAL_GATES_SOURCE_SURFACES", "module": "loopx/summary_all.py", "container": "list", "values": ["status attention queue", "quota should-run summaries", "active-state todo projections", "compact run-history projection consumed by status"]}, - {"name": "SOURCE_SURFACES", "module": "loopx/summary_all.py", "container": "list", "values": ["global registry compact status", "status attention queue", "quota should-run summaries", "active-state todo projections", "run history index summaries"]}, - {"name": "CODEX_THREAD_HOST_SURFACES", "module": "loopx/thread_agent_binding.py", "container": "frozenset", "values": ["codex-app", "codex-app-ssh", "codex-ide-plugin", "codex-cli-tui"]}, - {"name": "ALLOWED_TODO_SUGGESTION_SOURCES", "module": "loopx/todo_suggestion_prompt.py", "container": "tuple", "values": ["recent-repo", "issues-prs", "failing-checks", "todo-markers", "complexity-hotspots", "loopx-deferred", "docs-smokes"]}, - {"name": "ALLOWED_TODO_SUGGESTION_TRIGGERS", "module": "loopx/todo_suggestion_prompt.py", "container": "tuple", "values": ["user-requested", "post-connect", "no-runnable-todo", "repo-changed", "quality-watch"]}, - {"name": "DEFAULT_TODO_SUGGESTION_SOURCES", "module": "loopx/todo_suggestion_prompt.py", "container": "tuple", "values": ["recent-repo", "issues-prs", "failing-checks", "todo-markers", "loopx-deferred", "docs-smokes"]}, - {"name": "HOST_LOOP_UPDATE_STATUSES", "module": "loopx/upgrade.py", "container": "set", "values": ["missing", "partial", "stale", "unknown", "error", "unavailable"]}, - {"name": "PROJECT_POLICY_MARKERS", "module": "loopx/upgrade.py", "container": "tuple", "values": ["Current controller policy:", "Primary stability objective:", "Current controller policy"]}, - {"name": "COPY_DIRECTORIES", "module": "loopx/windows_install.py", "container": "tuple", "values": ["loopx", "scripts", "skills", "docs", "man", "examples", "apps", ".github"]}, - {"name": "COPY_FILES", "module": "loopx/windows_install.py", "container": "tuple", "values": ["README.md", "README.zh-CN.md", "LICENSE", "pyproject.toml"]}, - {"name": "REQUIRED_DEEP_CHECKS", "module": "loopx/windows_install.py", "container": "set", "values": ["command_package_same_root", "representative_cli_commands", "representative_cli_imports", "representative_package_paths"]}, - {"name": "ACTIVE_USER_SIMULATOR_ALLOWED_EVIDENCE_BASIS", "module": "loopx/worker_bridge.py", "container": "tuple", "values": ["public task prompt visible to worker", "worker public artifacts", "compact LoopX status and run metadata"]}, - {"name": "ACTIVE_USER_SIMULATOR_NO_ORACLE_AUDIT_KEYS", "module": "loopx/worker_bridge.py", "container": "tuple", "values": ["hidden_tests_visible", "expected_solution_visible", "benchmark_answer_key_visible", "credential_values_visible", "private_material_visible", "solution_patch_visible"]}, - {"name": "MANAGED_ENTRY_STATUSES", "module": "loopx/workflow_skill_install.py", "container": "set", "values": ["created", "updated", "unchanged", "upgraded_legacy_managed"]} - ], - "python_literal_aliases": [ - {"name": "CommandQualification", "module": "loopx/control_plane/testing/cli_output_budget.py", "values": ["qualified_default", "explicit_cold_path_exception"]}, - {"name": "OutputFormat", "module": "loopx/control_plane/testing/cli_output_budget.py", "values": ["json", "markdown"]}, - {"name": "QualificationPolicy", "module": "loopx/control_plane/testing/cli_output_budget.py", "values": ["absolute_hot_path", "baseline_and_growth", "explicit_limit_cold_path"]}, - {"name": "Metric", "module": "loopx/control_plane/testing/cli_output_differential.py", "values": ["chars", "utf8_bytes", "lines", "compact_payload_chars"]}, - {"name": "TodoCompletionProjectionSource", "module": "loopx/control_plane/todos/durable_completion.py", "values": ["materialized", "event_log"]}, - {"name": "TodoListProjectionView", "module": "loopx/control_plane/todos/list_projection.py", "values": ["agent_lane_hot_path", "explicit_limit_cold_path"]}, - {"name": "WorkspaceFileType", "module": "loopx/experiments/planner_worker/runtime.py", "values": ["regular", "symlink", "directory", "special", "deleted"]}, - {"name": "MentionIdentityKind", "module": "loopx/extensions/lark/outbound.py", "values": ["user_id", "open_id", "union_id"]} - ], - "typescript_const_arrays": [ - {"name": "AGENT_CONTEXT_PHASES", "module": "loopx/control_plane/agent_context.ts", "values": ["before_plan", "before_delegate", "after_delegate_result"]}, - {"name": "DELIVERY_WORKSPACE_IDENTITY_KINDS", "module": "loopx/control_plane/agents/delivery_workspace.ts", "values": ["git_repository", "local_goal"]}, - {"name": "DELIVERY_WORKSPACE_KINDS", "module": "loopx/control_plane/agents/delivery_workspace.ts", "values": ["canonical_checkout", "independent_git_worktree", "local_goal_workspace"]}, - {"name": "AUTHORITY_STORE_REQUIRED_GUARANTEES", "module": "loopx/control_plane/coordination/authority_store.ts", "values": ["atomic_event_projection_receipt_commit", "conditional_provider_revision", "durable_same_key_readback", "explicit_ambiguous_commit", "ordered_cursor_scan", "stable_store_lineage"]}, - {"name": "HANDOFF_MODES", "module": "loopx/control_plane/coordination/handoff_mode_policy.ts", "values": ["legacy", "soft_claim", "hard_lease"]}, - {"name": "SETTLEMENT_BINDING_KINDS", "module": "loopx/control_plane/effect_program.ts", "values": ["todo", "autonomous_replan", "unbound"]}, - {"name": "SETTLEMENT_FAILURE_KINDS", "module": "loopx/control_plane/effect_program.ts", "values": ["invalid_identity", "receipt_missing", "identity_mismatch", "writeback_missing", "writeback_rejected", "quota_spend_rejected", "terminal_closeout_rejected", "cancelled", "permission_denied", "budget_rejected", "effect_outcome_unknown"]}, - {"name": "SETTLEMENT_STEP_KINDS", "module": "loopx/control_plane/effect_program.ts", "values": ["validation", "durable_writeback", "quota_spend", "terminal_closeout"]}, - {"name": "EFFECT_RUNTIME_ERROR_KINDS", "module": "loopx/control_plane/effect_runtime_errors.ts", "values": ["request_rejected", "conflict", "io_transient", "io_permanent", "lock_timeout", "internal_failure"]}, - {"name": "GOAL_AMENDMENT_CLASSES", "module": "loopx/control_plane/goals/goal_amendment_proposal.ts", "values": ["lane_route", "shared_work_graph", "shared_acceptance", "protected_authority"]}, - {"name": "GOAL_AMENDMENT_PROPOSAL_ADMISSIONS", "module": "loopx/control_plane/goals/goal_amendment_proposal.ts", "values": ["admitted", "needs_rebase"]}, - {"name": "GOAL_AMENDMENT_PROPOSAL_ADMISSION_FACTS", "module": "loopx/control_plane/goals/goal_amendment_proposal.ts", "values": ["base_state_event_basis_sequence_behind_derived_head", "base_source_basis_digest_mismatch", "base_source_basis_unverifiable", "base_revision_basis_superseded"]}, - {"name": "REVISION_BASIS_VALUES", "module": "loopx/control_plane/goals/goal_amendment_proposal.ts", "values": ["state_event_log", "markdown_active_state", "canonical_todo_snapshot"]}, - {"name": "SHARED_GOAL_ALIGNMENT_CONFLICT_FACTS", "module": "loopx/control_plane/goals/shared_goal_alignment.ts", "values": ["frontier_basis_unverifiable", "lease_owner_mismatch", "open_lane_replan_obligation", "peer_claimed_lane_conflict"]}, - {"name": "SHARED_GOAL_ALIGNMENT_DRIFT_FACTS", "module": "loopx/control_plane/goals/shared_goal_alignment.ts", "values": ["frontier_basis_behind"]}, - {"name": "RECEIPT_BOUND_MONITOR_PHASES", "module": "loopx/control_plane/quota/settlement_phase.ts", "values": ["poll_due", "settlement_pending", "settled"]}, - {"name": "RECEIPT_BOUND_REPLAY_PHASES", "module": "loopx/control_plane/quota/settlement_phase.ts", "values": ["open", "settlement_pending", "settled"]}, - {"name": "DELIVERY_WORKSPACE_REQUIREMENTS", "module": "loopx/control_plane/quota/settlement_workspace_causality.ts", "values": ["required", "not_required", "unknown"]}, - {"name": "QUOTA_SPEND_SOURCES", "module": "loopx/control_plane/quota/spend_commit.ts", "values": ["heartbeat", "controller", "adapter", "visible-goal"]}, - {"name": "SCHEDULER_HEARTBEAT_COMMIT_OPERATIONS", "module": "loopx/control_plane/scheduler/heartbeat_commit.ts", "values": ["ack", "host_failure"]}, - {"name": "SCHEDULER_STATE_OPERATIONS", "module": "loopx/control_plane/scheduler/state_store.ts", "values": ["rrule_for_minutes", "normalize_rrule", "rrule_interval_minutes", "normalize_failure", "normalize_failures", "retain_failures", "merge_failure", "normalize_state", "build_state", "state_path"]}, - {"name": "SCHEDULER_CADENCE_TRANSITIONS", "module": "loopx/control_plane/scheduler/state_transition_rules.ts", "values": ["initial", "identity_reset", "retry_unacknowledged_failure", "hold_active_initial", "advance_after_interval", "hold_until_interval"]}, - {"name": "SCHEDULER_HOST_TRANSITIONS", "module": "loopx/control_plane/scheduler/state_transition_rules.ts", "values": ["apply_required", "host_match_ack_required", "recorded_failure_suppressed", "settled"]}, - {"name": "TODO_OWNERSHIP_INTENT_FIELDS", "module": "loopx/control_plane/todos/authoring_scope.ts", "values": ["claimed_by", "clear_claim", "excluded_agents"]}, - {"name": "TODO_COMPLETION_CONTINUATIONS", "module": "loopx/control_plane/todos/completion_state.ts", "values": ["active_goal", "successor", "no_followup"]}, - {"name": "TODO_COMPLETION_RECOVERIES", "module": "loopx/control_plane/todos/completion_state.ts", "values": ["same_turn_terminal_closeout", "lifecycle_reentry_terminal_closeout"]}, - {"name": "TODO_DECISION_METADATA_FIELDS", "module": "loopx/control_plane/todos/decision_metadata.ts", "values": ["decision_scope", "required_decision_scopes"]}, - {"name": "TODO_DECISION_SCOPE_GRANULARITIES", "module": "loopx/control_plane/todos/decision_metadata.ts", "values": ["action", "lane", "goal", "project", "global"]}, - {"name": "TODO_DECISION_SCOPE_KINDS", "module": "loopx/control_plane/todos/decision_metadata.ts", "values": ["private_read", "write_scope", "resource", "production", "public_claim", "direction", "other"]}, - {"name": "MONITOR_CONFIGURATION_FIELDS", "module": "loopx/control_plane/todos/monitor_metadata.ts", "values": ["target_key", "cadence", "next_due_at", "expires_at", "watch_only"]}, - {"name": "MONITOR_METADATA_FIELDS", "module": "loopx/control_plane/todos/monitor_metadata.ts", "values": ["target_key", "monitor_effect_id", "cadence", "next_due_at", "expires_at", "last_checked_at", "result_hash", "consecutive_no_change", "material_change", "material_change_generation", "max_no_change_before_replan", "watch_only"]}, - {"name": "TODO_RESUME_KINDS", "module": "loopx/control_plane/todos/resume_condition.ts", "values": ["todo_done", "pr_merged", "capacity_available", "monitor_changed", "resume_at"]}, - {"name": "TODO_WORK_REQUIREMENT_FIELDS", "module": "loopx/control_plane/todos/work_requirements.ts", "values": ["action_kind", "task_domain", "task_repository", "required_write_scopes", "required_capabilities", "target_capabilities", "explore_result_node_refs"]}, - {"name": "DELIVERY_BOUNDARIES", "module": "loopx/control_plane/turn_driver/delivery_continuity.ts", "values": ["in_flight_continuation", "semantic_closeout"]}, - {"name": "DELIVERY_CONTINUITY_PREEMPTIONS", "module": "loopx/control_plane/turn_driver/delivery_continuity.ts", "values": ["heartbeat_receipt", "blocking_work_lane", "autonomous_replan", "control_repair", "delivery_not_allowed"]}, - {"name": "TURN_RESULT_KINDS", "module": "loopx/control_plane/turn_driver/settlement.ts", "values": ["validated_progress", "validated_completion", "repair_required", "replan_required", "user_action_required", "wait", "iteration_failed", "host_failure", "validation_failed", "writeback_failed", "quota_spend_failed", "terminal_closeout_failed"]}, - {"name": "DELIVERY_OUTCOMES", "module": "loopx/control_plane/work_items/delivery_outcome.ts", "values": ["surface_only", "outcome_gap", "outcome_progress", "primary_goal_outcome"]}, - {"name": "MATERIAL_DELIVERY_OUTCOMES", "module": "loopx/control_plane/work_items/delivery_outcome.ts", "values": ["outcome_gap", "outcome_progress", "primary_goal_outcome"]}, - {"name": "TASK_LEASE_LIFECYCLE_OPERATIONS", "module": "loopx/control_plane/work_items/task_lease_lifecycle.ts", "values": ["renew", "transfer", "release", "terminal_verify", "holder_verify", "fence_close"]}, - {"name": "TASK_LEASE_LIFECYCLE_DECISION_OPERATIONS", "module": "loopx/control_plane/work_items/task_lease_lifecycle_decision.ts", "values": ["renew", "transfer", "release"]} - ], - "duplicate_definitions": { - "cross_runtime_twins": [ - {"name": "ACTION_PORTFOLIO_PLANNING_PACKET_REQUEST_SCHEMA", "is_schema_version": false, "value": "quota_planning_packet_request_v0", "modules": ["loopx/control_plane/coordination/coordination_state_contract.generated.ts", "loopx/control_plane/coordination/coordination_state_contract_generated.py"]}, - {"name": "ACTION_PORTFOLIO_PLANNING_PACKET_RESULT_SCHEMA", "is_schema_version": false, "value": "quota_planning_packet_v0", "modules": ["loopx/control_plane/coordination/coordination_state_contract.generated.ts", "loopx/control_plane/coordination/coordination_state_contract_generated.py"]}, - {"name": "ACTION_PORTFOLIO_SELECTION_REQUEST_SCHEMA", "is_schema_version": false, "value": "action_selection_qualification_request_v0", "modules": ["loopx/control_plane/coordination/coordination_state_contract.generated.ts", "loopx/control_plane/coordination/coordination_state_contract_generated.py"]}, - {"name": "ACTION_PORTFOLIO_SELECTION_RESULT_SCHEMA", "is_schema_version": false, "value": "action_selection_qualification_v0", "modules": ["loopx/control_plane/coordination/coordination_state_contract.generated.ts", "loopx/control_plane/coordination/coordination_state_contract_generated.py"]}, - {"name": "ACTION_SIGNATURE_COVERAGE_V0", "is_schema_version": false, "value": "turn_envelope_action_dimensions_v0", "modules": ["loopx/control_plane/quota/turn_envelope.py", "loopx/control_plane/quota/turn_envelope.ts"]}, - {"name": "ACTION_SIGNATURE_COVERAGE_V1", "is_schema_version": false, "value": "turn_envelope_action_dimensions_v1", "modules": ["loopx/control_plane/quota/turn_envelope.py", "loopx/control_plane/quota/turn_envelope.ts"]}, - {"name": "ACTION_SIGNATURE_COVERAGE_V2", "is_schema_version": false, "value": "turn_envelope_action_dimensions_v2", "modules": ["loopx/control_plane/quota/turn_envelope.py", "loopx/control_plane/quota/turn_envelope.ts"]}, - {"name": "ACTION_SIGNATURE_COVERAGE_V3", "is_schema_version": false, "value": "turn_envelope_action_dimensions_v3", "modules": ["loopx/control_plane/quota/turn_envelope.py", "loopx/control_plane/quota/turn_envelope.ts"]}, - {"name": "ACTION_SIGNATURE_COVERAGE_V4", "is_schema_version": false, "value": "turn_envelope_action_dimensions_v4", "modules": ["loopx/control_plane/quota/turn_envelope.py", "loopx/control_plane/quota/turn_envelope.ts"]}, - {"name": "ACTION_SIGNATURE_SCHEMA_VERSION", "is_schema_version": true, "value": "loopx_action_signature_v0", "modules": ["loopx/control_plane/quota/turn_envelope.py", "loopx/control_plane/quota/turn_envelope.ts"]}, - {"name": "APP_AUTOMATION_STATEFUL_BACKOFF_STATE_KEY", "is_schema_version": false, "value": "scheduler_hint.app_automation.stateful_backoff", "modules": ["loopx/control_plane/scheduler/state.py", "loopx/control_plane/scheduler/state_store.ts"]}, - {"name": "CAPABILITY_HOOK_INTENT_SCHEMA", "is_schema_version": false, "value": "loopx_capability_intent_v0", "modules": ["loopx/control_plane/coordination/coordination_state_contract.generated.ts", "loopx/control_plane/coordination/coordination_state_contract_generated.py"]}, - {"name": "CAPABILITY_HOOK_INTERACTION_RESULT_SCHEMA", "is_schema_version": false, "value": "loopx_interaction_projection_hook_result_v0", "modules": ["loopx/control_plane/coordination/coordination_state_contract.generated.ts", "loopx/control_plane/coordination/coordination_state_contract_generated.py"]}, - {"name": "CAPABILITY_HOOK_POST_WRITEBACK_INPUT_SCHEMA", "is_schema_version": false, "value": "loopx_post_writeback_capability_hook_input_v0", "modules": ["loopx/control_plane/coordination/coordination_state_contract.generated.ts", "loopx/control_plane/coordination/coordination_state_contract_generated.py"]}, - {"name": "CAPABILITY_HOOK_POST_WRITEBACK_RECEIPT_SCHEMA", "is_schema_version": false, "value": "loopx_post_writeback_capability_hook_receipt_v0", "modules": ["loopx/control_plane/coordination/coordination_state_contract.generated.ts", "loopx/control_plane/coordination/coordination_state_contract_generated.py"]}, - {"name": "CAPABILITY_HOOK_POST_WRITEBACK_REGISTRATION_SCHEMA", "is_schema_version": false, "value": "loopx_post_writeback_capability_hook_registration_v0", "modules": ["loopx/control_plane/coordination/coordination_state_contract.generated.ts", "loopx/control_plane/coordination/coordination_state_contract_generated.py"]}, - {"name": "CAPABILITY_HOOK_POST_WRITEBACK_RESULT_SCHEMA", "is_schema_version": false, "value": "loopx_post_writeback_capability_hook_result_v0", "modules": ["loopx/control_plane/coordination/coordination_state_contract.generated.ts", "loopx/control_plane/coordination/coordination_state_contract_generated.py"]}, - {"name": "CAPABILITY_HOOK_REGISTRATION_SCHEMA", "is_schema_version": false, "value": "loopx_capability_hook_registration_v0", "modules": ["loopx/control_plane/coordination/coordination_state_contract.generated.ts", "loopx/control_plane/coordination/coordination_state_contract_generated.py"]}, - {"name": "CAPABILITY_HOOK_TURN_START_REGISTRATION_SCHEMA", "is_schema_version": false, "value": "loopx_turn_start_capability_hook_registration_v1", "modules": ["loopx/control_plane/coordination/coordination_state_contract.generated.ts", "loopx/control_plane/coordination/coordination_state_contract_generated.py"]}, - {"name": "CAPABILITY_HOOK_TURN_START_RESULT_SCHEMA", "is_schema_version": false, "value": "loopx_turn_start_capability_hook_result_v0", "modules": ["loopx/control_plane/coordination/coordination_state_contract.generated.ts", "loopx/control_plane/coordination/coordination_state_contract_generated.py"]}, - {"name": "CODEX_APP_SURFACE", "is_schema_version": false, "value": "codex_app", "modules": ["loopx/control_plane/scheduler/state.py", "loopx/control_plane/scheduler/state_store.ts"]}, - {"name": "CONTRACT_CAPSULE_SCHEMA_VERSION", "is_schema_version": true, "value": "loopx_contract_capsule_v0", "modules": ["loopx/control_plane/quota/turn_envelope.py", "loopx/control_plane/quota/turn_envelope.ts"]}, - {"name": "COORDINATION_RUNTIME_SHADOW_BOOTSTRAP_REQUEST_SCHEMA", "is_schema_version": false, "value": "loopx_coordination_runtime_shadow_bootstrap_v0", "modules": ["loopx/control_plane/coordination/coordination_state_contract.generated.ts", "loopx/control_plane/coordination/coordination_state_contract_generated.py"]}, - {"name": "COORDINATION_RUNTIME_SHADOW_BOOTSTRAP_RESULT_SCHEMA", "is_schema_version": false, "value": "loopx_coordination_runtime_shadow_bootstrap_result_v0", "modules": ["loopx/control_plane/coordination/coordination_state_contract.generated.ts", "loopx/control_plane/coordination/coordination_state_contract_generated.py"]}, - {"name": "COORDINATION_RUNTIME_SHADOW_COMMIT_REQUEST_SCHEMA", "is_schema_version": false, "value": "loopx_coordination_runtime_shadow_commit_v0", "modules": ["loopx/control_plane/coordination/coordination_state_contract.generated.ts", "loopx/control_plane/coordination/coordination_state_contract_generated.py"]}, - {"name": "COORDINATION_RUNTIME_SHADOW_COMMIT_RESULT_SCHEMA", "is_schema_version": false, "value": "loopx_coordination_runtime_shadow_result_v0", "modules": ["loopx/control_plane/coordination/coordination_state_contract.generated.ts", "loopx/control_plane/coordination/coordination_state_contract_generated.py"]}, - {"name": "COORDINATION_RUNTIME_SHADOW_INSPECT_REQUEST_SCHEMA", "is_schema_version": false, "value": "loopx_coordination_runtime_shadow_inspect_v0", "modules": ["loopx/control_plane/coordination/coordination_state_contract.generated.ts", "loopx/control_plane/coordination/coordination_state_contract_generated.py"]}, - {"name": "COORDINATION_RUNTIME_SHADOW_INSPECT_RESULT_SCHEMA", "is_schema_version": false, "value": "loopx_coordination_runtime_shadow_inspection_v0", "modules": ["loopx/control_plane/coordination/coordination_state_contract.generated.ts", "loopx/control_plane/coordination/coordination_state_contract_generated.py"]}, - {"name": "COORDINATION_RUNTIME_SHADOW_QUALIFY_REQUEST_SCHEMA", "is_schema_version": false, "value": "loopx_coordination_runtime_shadow_qualify_v0", "modules": ["loopx/control_plane/coordination/coordination_state_contract.generated.ts", "loopx/control_plane/coordination/coordination_state_contract_generated.py"]}, - {"name": "COORDINATION_RUNTIME_SHADOW_QUALIFY_RESULT_SCHEMA", "is_schema_version": false, "value": "loopx_coordination_runtime_shadow_qualification_v0", "modules": ["loopx/control_plane/coordination/coordination_state_contract.generated.ts", "loopx/control_plane/coordination/coordination_state_contract_generated.py"]}, - {"name": "COORDINATION_RUNTIME_SHADOW_RECEIPT_SCHEMA", "is_schema_version": false, "value": "loopx_coordination_runtime_shadow_receipt_v0", "modules": ["loopx/control_plane/coordination/coordination_state_contract.generated.ts", "loopx/control_plane/coordination/coordination_state_contract_generated.py"]}, - {"name": "COORDINATION_RUNTIME_SHADOW_ROLLBACK_REQUEST_SCHEMA", "is_schema_version": false, "value": "loopx_coordination_runtime_shadow_rollback_v0", "modules": ["loopx/control_plane/coordination/coordination_state_contract.generated.ts", "loopx/control_plane/coordination/coordination_state_contract_generated.py"]}, - {"name": "COORDINATION_RUNTIME_SHADOW_ROLLBACK_RESULT_SCHEMA", "is_schema_version": false, "value": "loopx_coordination_runtime_shadow_rollback_result_v0", "modules": ["loopx/control_plane/coordination/coordination_state_contract.generated.ts", "loopx/control_plane/coordination/coordination_state_contract_generated.py"]}, - {"name": "COORDINATION_RUNTIME_SHADOW_TODO_READ_REQUEST_SCHEMA", "is_schema_version": false, "value": "loopx_coordination_runtime_shadow_todo_read_v0", "modules": ["loopx/control_plane/coordination/coordination_state_contract.generated.ts", "loopx/control_plane/coordination/coordination_state_contract_generated.py"]}, - {"name": "COORDINATION_RUNTIME_SHADOW_TODO_READ_RESULT_SCHEMA", "is_schema_version": false, "value": "loopx_coordination_runtime_shadow_todo_read_result_v0", "modules": ["loopx/control_plane/coordination/coordination_state_contract.generated.ts", "loopx/control_plane/coordination/coordination_state_contract_generated.py"]}, - {"name": "DELIVERY_BOUNDARY_RESULT_SCHEMA", "is_schema_version": false, "value": "loopx_delivery_boundary_result_v0", "modules": ["loopx/control_plane/coordination/coordination_state_contract.generated.ts", "loopx/control_plane/coordination/coordination_state_contract_generated.py"]}, - {"name": "DELIVERY_CONTINUITY_RESULT_SCHEMA", "is_schema_version": false, "value": "loopx_delivery_continuity_result_v0", "modules": ["loopx/control_plane/coordination/coordination_state_contract.generated.ts", "loopx/control_plane/coordination/coordination_state_contract_generated.py"]}, - {"name": "DELIVERY_ROUTING_REQUEST_SCHEMA", "is_schema_version": false, "value": "loopx_delivery_routing_request_v0", "modules": ["loopx/control_plane/coordination/coordination_state_contract.generated.ts", "loopx/control_plane/coordination/coordination_state_contract_generated.py"]}, - {"name": "DELIVERY_ROUTING_RESULT_SCHEMA", "is_schema_version": false, "value": "loopx_delivery_routing_result_v0", "modules": ["loopx/control_plane/coordination/coordination_state_contract.generated.ts", "loopx/control_plane/coordination/coordination_state_contract_generated.py"]}, - {"name": "DELIVERY_WORKSPACE_CAUSALITY_REQUEST_SCHEMA", "is_schema_version": false, "value": "loopx_delivery_workspace_causality_request_v0", "modules": ["loopx/control_plane/coordination/coordination_state_contract.generated.ts", "loopx/control_plane/coordination/coordination_state_contract_generated.py"]}, - {"name": "DELIVERY_WORKSPACE_CAUSALITY_RESULT_SCHEMA", "is_schema_version": false, "value": "loopx_delivery_workspace_causality_result_v0", "modules": ["loopx/control_plane/coordination/coordination_state_contract.generated.ts", "loopx/control_plane/coordination/coordination_state_contract_generated.py"]}, - {"name": "DELIVERY_WORKSPACE_CAUSALITY_SCHEMA", "is_schema_version": false, "value": "delivery_workspace_causality_v0", "modules": ["loopx/control_plane/coordination/coordination_state_contract.generated.ts", "loopx/control_plane/coordination/coordination_state_contract_generated.py"]}, - {"name": "DELIVERY_WORKSPACE_LEGACY_RECEIPT_EVIDENCE_SCHEMA", "is_schema_version": false, "value": "legacy_settlement_receipt_evidence_v0", "modules": ["loopx/control_plane/coordination/coordination_state_contract.generated.ts", "loopx/control_plane/coordination/coordination_state_contract_generated.py"]}, - {"name": "DELIVERY_WORKSPACE_RESOLUTION_SCHEMA", "is_schema_version": false, "value": "delivery_workspace_resolution_v0", "modules": ["loopx/control_plane/coordination/coordination_state_contract.generated.ts", "loopx/control_plane/coordination/coordination_state_contract_generated.py"]}, - {"name": "DELIVERY_WORKSPACE_SETTLEMENT_REQUIREMENT_SCHEMA", "is_schema_version": false, "value": "settlement_workspace_requirement_v0", "modules": ["loopx/control_plane/coordination/coordination_state_contract.generated.ts", "loopx/control_plane/coordination/coordination_state_contract_generated.py"]}, - {"name": "DELIVERY_WORKSPACE_SNAPSHOT_LEGACY_SNAPSHOT_SCHEMA", "is_schema_version": false, "value": "delivery_workspace_v0", "modules": ["loopx/control_plane/coordination/coordination_state_contract.generated.ts", "loopx/control_plane/coordination/coordination_state_contract_generated.py"]}, - {"name": "DELIVERY_WORKSPACE_SNAPSHOT_REQUEST_SCHEMA", "is_schema_version": false, "value": "loopx_delivery_workspace_request_v0", "modules": ["loopx/control_plane/coordination/coordination_state_contract.generated.ts", "loopx/control_plane/coordination/coordination_state_contract_generated.py"]}, - {"name": "DELIVERY_WORKSPACE_SNAPSHOT_RESULT_SCHEMA", "is_schema_version": false, "value": "loopx_delivery_workspace_result_v0", "modules": ["loopx/control_plane/coordination/coordination_state_contract.generated.ts", "loopx/control_plane/coordination/coordination_state_contract_generated.py"]}, - {"name": "DELIVERY_WORKSPACE_SNAPSHOT_SNAPSHOT_SCHEMA", "is_schema_version": false, "value": "delivery_workspace_v1", "modules": ["loopx/control_plane/coordination/coordination_state_contract.generated.ts", "loopx/control_plane/coordination/coordination_state_contract_generated.py"]}, - {"name": "GOAL_ACTION_CATALOG_SCHEMA_VERSION", "is_schema_version": true, "value": "loopx_goal_action_catalog_v1", "modules": ["loopx/control_plane/goals/operator_actions.py", "loopx/control_plane/goals/operator_actions.ts"]}, - {"name": "GOAL_ACTION_PROJECTION_REQUEST_SCHEMA_VERSION", "is_schema_version": true, "value": "loopx_goal_action_projection_request_v2", "modules": ["loopx/control_plane/goals/operator_actions.py", "loopx/control_plane/goals/operator_actions.ts"]}, - {"name": "GOAL_AMENDMENT_PROPOSAL_ADMISSION_SCHEMA_VERSION", "is_schema_version": true, "value": "goal_amendment_proposal_admission_v0", "modules": ["loopx/control_plane/goals/goal_amendment_proposal.py", "loopx/control_plane/goals/goal_amendment_proposal.ts"]}, - {"name": "GOAL_AMENDMENT_PROPOSAL_REQUEST_SCHEMA_VERSION", "is_schema_version": true, "value": "goal_amendment_proposal_request_v0", "modules": ["loopx/control_plane/goals/goal_amendment_proposal.py", "loopx/control_plane/goals/goal_amendment_proposal.ts"]}, - {"name": "GOAL_AMENDMENT_PROPOSAL_SCHEMA_VERSION", "is_schema_version": true, "value": "goal_amendment_proposal_v0", "modules": ["loopx/control_plane/goals/goal_amendment_proposal.py", "loopx/control_plane/goals/goal_amendment_proposal.ts"]}, - {"name": "GOVERNED_CAPABILITY_LIFECYCLE_PACKET_SCHEMA_VERSION", "is_schema_version": true, "value": "loopx_governed_capability_lifecycle_packet_v0", "modules": ["loopx/control_plane/governed_capability.ts", "loopx/extensions/governed_capability_execution.py"]}, - {"name": "GOVERNED_CAPABILITY_LIFECYCLE_REDUCTION_SCHEMA_VERSION", "is_schema_version": true, "value": "loopx_governed_capability_lifecycle_reduction_v0", "modules": ["loopx/control_plane/governed_capability.ts", "loopx/extensions/governed_capability_execution.py"]}, - {"name": "GOVERNED_CAPABILITY_RECEIPT_SCHEMA_VERSION", "is_schema_version": true, "value": "loopx_governed_capability_execution_receipt_v0", "modules": ["loopx/control_plane/governed_capability.ts", "loopx/extensions/governed_capability_execution.py"]}, - {"name": "GOVERNED_CAPABILITY_RUN_SCHEMA_VERSION", "is_schema_version": true, "value": "loopx_governed_capability_run_v0", "modules": ["loopx/control_plane/governed_capability.ts", "loopx/extensions/governed_capability_execution.py"]}, - {"name": "HOST_ADAPTER_SETTLEMENT_SCHEMA_VERSION", "is_schema_version": true, "value": "host_adapter_todo_settlement_v0", "modules": ["loopx/control_plane/host_adapter_settlement.py", "loopx/control_plane/turn_driver/host_todo_completion.ts"]}, - {"name": "HOST_TODO_COMPLETION_REDUCTION_SCHEMA_VERSION", "is_schema_version": true, "value": "loopx_host_todo_completion_reduction_v0", "modules": ["loopx/control_plane/host_adapter_settlement.py", "loopx/control_plane/turn_driver/host_todo_completion.ts"]}, - {"name": "HOST_TODO_COMPLETION_TRANSACTION_SCHEMA_VERSION", "is_schema_version": true, "value": "loopx_host_todo_completion_transaction_v0", "modules": ["loopx/control_plane/host_adapter_settlement.py", "loopx/control_plane/turn_driver/host_todo_completion.ts"]}, - {"name": "INTERACTION_CONTRACT_SCHEMA_VERSION", "is_schema_version": true, "value": "loopx_interaction_contract_v0", "modules": ["loopx/control_plane/work_items/interaction_contract.py", "loopx/control_plane/work_items/interaction_contract.ts"]}, - {"name": "LEASE_PARTITION", "is_schema_version": false, "value": "leases", "modules": ["loopx/control_plane/coordination/local_authority_shadow_outbox.ts", "loopx/control_plane/coordination/local_authority_shadow_projection.py"]}, - {"name": "LEGACY_CODEX_APP_STATEFUL_BACKOFF_STATE_KEY", "is_schema_version": false, "value": "scheduler_hint.codex_app.stateful_backoff", "modules": ["loopx/control_plane/scheduler/state.py", "loopx/control_plane/scheduler/state_store.ts"]}, - {"name": "LEGACY_COORDINATION_WRITER_FENCE_ENGAGE_REQUEST_SCHEMA", "is_schema_version": false, "value": "loopx_legacy_coordination_writer_fence_engage_request_v0", "modules": ["loopx/control_plane/coordination/coordination_state_contract.generated.ts", "loopx/control_plane/coordination/coordination_state_contract_generated.py"]}, - {"name": "LEGACY_COORDINATION_WRITER_FENCE_RESULT_SCHEMA", "is_schema_version": false, "value": "loopx_legacy_coordination_writer_fence_result_v0", "modules": ["loopx/control_plane/coordination/coordination_state_contract.generated.ts", "loopx/control_plane/coordination/coordination_state_contract_generated.py"]}, - {"name": "LEGACY_COORDINATION_WRITER_FENCE_SCHEMA", "is_schema_version": false, "value": "loopx_legacy_coordination_writer_fence_v0", "modules": ["loopx/control_plane/coordination/coordination_state_contract.generated.ts", "loopx/control_plane/coordination/coordination_state_contract_generated.py"]}, - {"name": "LEGACY_COORDINATION_WRITE_CHECK_REQUEST_SCHEMA", "is_schema_version": false, "value": "loopx_legacy_coordination_write_check_request_v0", "modules": ["loopx/control_plane/coordination/coordination_state_contract.generated.ts", "loopx/control_plane/coordination/coordination_state_contract_generated.py"]}, - {"name": "LEGACY_COORDINATION_WRITE_CHECK_RESULT_SCHEMA", "is_schema_version": false, "value": "loopx_legacy_coordination_write_check_result_v0", "modules": ["loopx/control_plane/coordination/coordination_state_contract.generated.ts", "loopx/control_plane/coordination/coordination_state_contract_generated.py"]}, - {"name": "LEGACY_WRITER_FENCED_REMEDIATION", "is_schema_version": false, "value": "legacy coordination writer is fenced; use the promoted canonical authority ({authority_mode}) for goal {goal_id}; fence {fence_id}; the primary record was not changed", "modules": ["loopx/control_plane/coordination/legacy_writer_fence.py", "loopx/control_plane/coordination/legacy_writer_fence.ts"]}, - {"name": "LOCAL_AUTHORITY_SHADOW_BINDING_SCHEMA", "is_schema_version": false, "value": "loopx_coordination_runtime_shadow_binding_v0", "modules": ["loopx/control_plane/coordination/coordination_state_contract.generated.ts", "loopx/control_plane/coordination/coordination_state_contract_generated.py"]}, - {"name": "LOCAL_AUTHORITY_SHADOW_COMMIT_ENTRY_REQUEST_SCHEMA", "is_schema_version": false, "value": "loopx_coordination_runtime_shadow_commit_entry_request_v1", "modules": ["loopx/control_plane/coordination/coordination_state_contract.generated.ts", "loopx/control_plane/coordination/coordination_state_contract_generated.py"]}, - {"name": "LOCAL_AUTHORITY_SHADOW_COMMIT_ENTRY_RESULT_SCHEMA", "is_schema_version": false, "value": "loopx_coordination_runtime_shadow_commit_entry_result_v0", "modules": ["loopx/control_plane/coordination/coordination_state_contract.generated.ts", "loopx/control_plane/coordination/coordination_state_contract_generated.py"]}, - {"name": "LOCAL_AUTHORITY_SHADOW_CONFIG_SCHEMA", "is_schema_version": false, "value": "loopx_local_authority_shadow_config_v0", "modules": ["loopx/control_plane/coordination/coordination_state_contract.generated.ts", "loopx/control_plane/coordination/coordination_state_contract_generated.py"]}, - {"name": "LOCAL_AUTHORITY_SHADOW_DRAIN_CURSOR_SCHEMA", "is_schema_version": false, "value": "loopx_local_authority_shadow_drain_cursor_v0", "modules": ["loopx/control_plane/coordination/coordination_state_contract.generated.ts", "loopx/control_plane/coordination/coordination_state_contract_generated.py"]}, - {"name": "LOCAL_AUTHORITY_SHADOW_EVENT_SCHEMA", "is_schema_version": false, "value": "loopx_coordination_runtime_shadow_outbox_event_v1", "modules": ["loopx/control_plane/coordination/coordination_state_contract.generated.ts", "loopx/control_plane/coordination/coordination_state_contract_generated.py"]}, - {"name": "LOCAL_AUTHORITY_SHADOW_EVIDENCE_SCHEMA", "is_schema_version": false, "value": "loopx_local_authority_shadow_evidence_v0", "modules": ["loopx/control_plane/coordination/coordination_state_contract.generated.ts", "loopx/control_plane/coordination/coordination_state_contract_generated.py"]}, - {"name": "LOCAL_AUTHORITY_SHADOW_OBSERVATION_RECEIPT_SCHEMA", "is_schema_version": false, "value": "loopx_local_authority_shadow_observation_receipt_v0", "modules": ["loopx/control_plane/coordination/coordination_state_contract.generated.ts", "loopx/control_plane/coordination/coordination_state_contract_generated.py"]}, - {"name": "LOCAL_AUTHORITY_SHADOW_OUTBOX_COMMIT_SCHEMA", "is_schema_version": false, "value": "loopx_local_authority_shadow_outbox_commit_v1", "modules": ["loopx/control_plane/coordination/coordination_state_contract.generated.ts", "loopx/control_plane/coordination/coordination_state_contract_generated.py"]}, - {"name": "LOCAL_AUTHORITY_SHADOW_OUTBOX_ENTRY_SCHEMA", "is_schema_version": false, "value": "loopx_local_authority_shadow_outbox_entry_v1", "modules": ["loopx/control_plane/coordination/coordination_state_contract.generated.ts", "loopx/control_plane/coordination/coordination_state_contract_generated.py"]}, - {"name": "LOCAL_AUTHORITY_SHADOW_PROJECTION_SCHEMA", "is_schema_version": false, "value": "loopx_local_authority_shadow_projection_v0", "modules": ["loopx/control_plane/coordination/coordination_state_contract.generated.ts", "loopx/control_plane/coordination/coordination_state_contract_generated.py"]}, - {"name": "LOCAL_AUTHORITY_SHADOW_READ_REQUEST_SCHEMA", "is_schema_version": false, "value": "loopx_coordination_runtime_shadow_outbox_read_v0", "modules": ["loopx/control_plane/coordination/coordination_state_contract.generated.ts", "loopx/control_plane/coordination/coordination_state_contract_generated.py"]}, - {"name": "LOCAL_AUTHORITY_SHADOW_READ_RESULT_SCHEMA", "is_schema_version": false, "value": "loopx_coordination_runtime_shadow_outbox_read_result_v0", "modules": ["loopx/control_plane/coordination/coordination_state_contract.generated.ts", "loopx/control_plane/coordination/coordination_state_contract_generated.py"]}, - {"name": "LOCAL_AUTHORITY_SHADOW_REQUEST_SCHEMA", "is_schema_version": false, "value": "loopx_local_authority_shadow_request_v0", "modules": ["loopx/control_plane/coordination/coordination_state_contract.generated.ts", "loopx/control_plane/coordination/coordination_state_contract_generated.py"]}, - {"name": "LOCAL_AUTHORITY_SHADOW_TRANSACTION_EVIDENCE_SCHEMA", "is_schema_version": false, "value": "loopx_local_authority_shadow_evidence_v1", "modules": ["loopx/control_plane/coordination/coordination_state_contract.generated.ts", "loopx/control_plane/coordination/coordination_state_contract_generated.py"]}, - {"name": "LOCAL_AUTHORITY_SHADOW_TRANSACTION_PROJECTION_SCHEMA", "is_schema_version": false, "value": "loopx_coordination_runtime_shadow_projection_v0", "modules": ["loopx/control_plane/coordination/coordination_state_contract.generated.ts", "loopx/control_plane/coordination/coordination_state_contract_generated.py"]}, - {"name": "LOCAL_AUTHORITY_SHADOW_TRANSACTION_RECEIPT_SCHEMA", "is_schema_version": false, "value": "loopx_coordination_runtime_shadow_outbox_receipt_v1", "modules": ["loopx/control_plane/coordination/coordination_state_contract.generated.ts", "loopx/control_plane/coordination/coordination_state_contract_generated.py"]}, - {"name": "LOCAL_COORDINATION_MUTATION_REQUEST_SCHEMA", "is_schema_version": false, "value": "loopx_local_coordination_mutation_request_v0", "modules": ["loopx/control_plane/coordination/coordination_state_contract.generated.ts", "loopx/control_plane/coordination/coordination_state_contract_generated.py"]}, - {"name": "LOCAL_COORDINATION_MUTATION_RESULT_SCHEMA", "is_schema_version": false, "value": "loopx_local_coordination_mutation_result_v0", "modules": ["loopx/control_plane/coordination/coordination_state_contract.generated.ts", "loopx/control_plane/coordination/coordination_state_contract_generated.py"]}, - {"name": "LOCAL_COORDINATION_PROMOTION_RECEIPT_SCHEMA", "is_schema_version": false, "value": "loopx_local_coordination_promotion_receipt_v0", "modules": ["loopx/control_plane/coordination/coordination_state_contract.generated.ts", "loopx/control_plane/coordination/coordination_state_contract_generated.py"]}, - {"name": "LOCAL_COORDINATION_PROMOTION_REQUEST_SCHEMA", "is_schema_version": false, "value": "loopx_local_coordination_promotion_request_v0", "modules": ["loopx/control_plane/coordination/coordination_state_contract.generated.ts", "loopx/control_plane/coordination/coordination_state_contract_generated.py"]}, - {"name": "LOCAL_COORDINATION_PROMOTION_RESULT_SCHEMA", "is_schema_version": false, "value": "loopx_local_coordination_promotion_result_v0", "modules": ["loopx/control_plane/coordination/coordination_state_contract.generated.ts", "loopx/control_plane/coordination/coordination_state_contract_generated.py"]}, - {"name": "LOCAL_COORDINATION_TODO_CLAIM_REQUEST_SCHEMA", "is_schema_version": false, "value": "loopx_local_coordination_todo_claim_request_v0", "modules": ["loopx/control_plane/coordination/local_authority.py", "loopx/control_plane/coordination/local_authority_runtime.ts"]}, - {"name": "LOCAL_COORDINATION_TODO_LIST_REQUEST_SCHEMA", "is_schema_version": false, "value": "loopx_local_coordination_todo_list_request_v0", "modules": ["loopx/control_plane/coordination/coordination_state_contract.generated.ts", "loopx/control_plane/coordination/coordination_state_contract_generated.py"]}, - {"name": "LOCAL_COORDINATION_TODO_LIST_RESULT_SCHEMA", "is_schema_version": false, "value": "loopx_local_coordination_todo_list_result_v0", "modules": ["loopx/control_plane/coordination/coordination_state_contract.generated.ts", "loopx/control_plane/coordination/coordination_state_contract_generated.py"]}, - {"name": "LOCAL_COORDINATION_TODO_READ_REQUEST_SCHEMA", "is_schema_version": false, "value": "loopx_local_coordination_todo_read_request_v0", "modules": ["loopx/control_plane/coordination/coordination_state_contract.generated.ts", "loopx/control_plane/coordination/coordination_state_contract_generated.py"]}, - {"name": "LOCAL_COORDINATION_TODO_READ_RESULT_SCHEMA", "is_schema_version": false, "value": "loopx_local_coordination_todo_read_result_v0", "modules": ["loopx/control_plane/coordination/coordination_state_contract.generated.ts", "loopx/control_plane/coordination/coordination_state_contract_generated.py"]}, - {"name": "POST_WRITEBACK_HOOK_DISPATCH_SCHEMA_VERSION", "is_schema_version": true, "value": "loopx_post_writeback_capability_hook_dispatch_v0", "modules": ["loopx/control_plane/capability_hooks.py", "loopx/control_plane/post_writeback_hook_transaction.ts"]}, - {"name": "QUOTA_MONITOR_POLL_CLASSIFICATION", "is_schema_version": false, "value": "quota_monitor_poll", "modules": ["loopx/control_plane/quota/monitor_poll.py", "loopx/control_plane/quota/monitor_poll_commit.ts"]}, - {"name": "QUOTA_MONITOR_POLL_COMMIT_REQUEST_SCHEMA", "is_schema_version": false, "value": "loopx_quota_monitor_poll_commit_request_v0", "modules": ["loopx/control_plane/quota/monitor_poll.py", "loopx/control_plane/quota/monitor_poll_commit.ts"]}, - {"name": "QUOTA_MONITOR_POLL_COMMIT_RESULT_SCHEMA", "is_schema_version": false, "value": "loopx_quota_monitor_poll_commit_result_v0", "modules": ["loopx/control_plane/quota/monitor_poll.py", "loopx/control_plane/quota/monitor_poll_commit.ts"]}, - {"name": "QUOTA_SETTLEMENT_READBACK_REQUEST_SCHEMA", "is_schema_version": false, "value": "loopx_quota_settlement_readback_request_v0", "modules": ["loopx/control_plane/quota/settlement.py", "loopx/control_plane/quota/settlement_readback.ts"]}, - {"name": "QUOTA_SETTLEMENT_READBACK_RESULT_SCHEMA", "is_schema_version": false, "value": "loopx_quota_settlement_readback_result_v0", "modules": ["loopx/control_plane/quota/settlement.py", "loopx/control_plane/quota/settlement_readback.ts"]}, - {"name": "QUOTA_SLOT_SPENT_CLASSIFICATION", "is_schema_version": false, "value": "quota_slot_spent", "modules": ["loopx/control_plane/quota/slot_accounting.py", "loopx/control_plane/quota/spend_commit.ts"]}, - {"name": "QUOTA_SLOT_VOIDED_CLASSIFICATION", "is_schema_version": false, "value": "quota_slot_voided", "modules": ["loopx/control_plane/quota/slot_accounting.py", "loopx/control_plane/quota/void_commit.ts"]}, - {"name": "QUOTA_SPEND_COMMIT_REQUEST_SCHEMA", "is_schema_version": false, "value": "loopx_quota_spend_commit_request_v0", "modules": ["loopx/control_plane/quota/spend_commit.py", "loopx/control_plane/quota/spend_commit.ts"]}, - {"name": "QUOTA_SPEND_COMMIT_RESULT_SCHEMA", "is_schema_version": false, "value": "loopx_quota_spend_commit_result_v0", "modules": ["loopx/control_plane/quota/spend_commit.py", "loopx/control_plane/quota/spend_commit.ts"]}, - {"name": "QUOTA_VOID_COMMIT_REQUEST_SCHEMA", "is_schema_version": false, "value": "loopx_quota_void_commit_request_v0", "modules": ["loopx/control_plane/quota/void_commit.py", "loopx/control_plane/quota/void_commit.ts"]}, - {"name": "QUOTA_VOID_COMMIT_RESULT_SCHEMA", "is_schema_version": false, "value": "loopx_quota_void_commit_result_v0", "modules": ["loopx/control_plane/quota/void_commit.py", "loopx/control_plane/quota/void_commit.ts"]}, - {"name": "REFRESH_RECOMMENDATION_REQUEST_SCHEMA_VERSION", "is_schema_version": true, "value": "refresh_recommendation_request_v0", "modules": ["loopx/control_plane/work_items/refresh_recommendation.py", "loopx/control_plane/work_items/refresh_recommendation.ts"]}, - {"name": "REFRESH_RECOMMENDATION_SCHEMA_VERSION", "is_schema_version": true, "value": "refresh_recommendation_v0", "modules": ["loopx/control_plane/work_items/refresh_recommendation.py", "loopx/control_plane/work_items/refresh_recommendation.ts"]}, - {"name": "REPLAN_SETTLEMENT_LIFECYCLE_REENTRY_REQUEST_SCHEMA", "is_schema_version": false, "value": "loopx_todo_lifecycle_reentry_request_v0", "modules": ["loopx/control_plane/coordination/coordination_state_contract.generated.ts", "loopx/control_plane/coordination/coordination_state_contract_generated.py"]}, - {"name": "REPLAN_SETTLEMENT_LIFECYCLE_REENTRY_RESULT_SCHEMA", "is_schema_version": false, "value": "todo_lifecycle_settlement_reentry_v0", "modules": ["loopx/control_plane/coordination/coordination_state_contract.generated.ts", "loopx/control_plane/coordination/coordination_state_contract_generated.py"]}, - {"name": "REPLAN_SETTLEMENT_REQUEST_SCHEMA", "is_schema_version": false, "value": "loopx_replan_settlement_request_v0", "modules": ["loopx/control_plane/coordination/coordination_state_contract.generated.ts", "loopx/control_plane/coordination/coordination_state_contract_generated.py"]}, - {"name": "REPLAN_SETTLEMENT_RESULT_SCHEMA", "is_schema_version": false, "value": "replan_settlement_contract_v0", "modules": ["loopx/control_plane/coordination/coordination_state_contract.generated.ts", "loopx/control_plane/coordination/coordination_state_contract_generated.py"]}, - {"name": "SCHEDULER_HOST_UPDATE_FAILURE_SCHEMA_VERSION", "is_schema_version": true, "value": "scheduler_host_update_failure_v0", "modules": ["loopx/control_plane/scheduler/state.py", "loopx/control_plane/scheduler/state_store.ts"]}, - {"name": "SCHEDULER_STATE_OPERATION_REQUEST_SCHEMA", "is_schema_version": false, "value": "loopx_scheduler_state_operation_request_v0", "modules": ["loopx/control_plane/scheduler/state.py", "loopx/control_plane/scheduler/state_store.ts"]}, - {"name": "SCHEDULER_STATE_OPERATION_RESULT_SCHEMA", "is_schema_version": false, "value": "loopx_scheduler_state_operation_result_v0", "modules": ["loopx/control_plane/scheduler/state.py", "loopx/control_plane/scheduler/state_store.ts"]}, - {"name": "SCHEDULER_STATE_SCHEMA_VERSION", "is_schema_version": true, "value": "loopx_scheduler_state_v0", "modules": ["loopx/control_plane/scheduler/state.py", "loopx/control_plane/scheduler/state_store.ts"]}, - {"name": "SCHEDULER_STATE_STORE_REQUEST_SCHEMA", "is_schema_version": false, "value": "loopx_scheduler_state_store_request_v0", "modules": ["loopx/control_plane/scheduler/state.py", "loopx/control_plane/scheduler/state_store.ts"]}, - {"name": "SCHEDULER_STATE_STORE_RESULT_SCHEMA", "is_schema_version": false, "value": "loopx_scheduler_state_store_result_v0", "modules": ["loopx/control_plane/scheduler/state.py", "loopx/control_plane/scheduler/state_store.ts"]}, - {"name": "SCHEDULER_STATE_TRANSITION_REQUEST_SCHEMA", "is_schema_version": false, "value": "loopx_scheduler_state_transition_request_v0", "modules": ["loopx/control_plane/scheduler/state_transition_rules.py", "loopx/control_plane/scheduler/state_transition_rules.ts"]}, - {"name": "SCHEDULER_STATE_TRANSITION_RESULT_SCHEMA", "is_schema_version": false, "value": "loopx_scheduler_state_transition_result_v0", "modules": ["loopx/control_plane/scheduler/state_transition_rules.py", "loopx/control_plane/scheduler/state_transition_rules.ts"]}, - {"name": "SCOPED_SETTLEMENT_IDENTITY_SCHEMA_VERSION", "is_schema_version": true, "value": "quota_settlement_identity_v1", "modules": ["loopx/control_plane/effect_program.py", "loopx/control_plane/effect_program.ts"]}, - {"name": "SEMANTIC_REPLAN_GUARD_SCHEMA", "is_schema_version": false, "value": "semantic_replan_guard_v0", "modules": ["loopx/control_plane/quota/settlement.py", "loopx/control_plane/quota/settlement_readback.ts"]}, - {"name": "SETTLEMENT_IDENTITY_SCHEMA_VERSION", "is_schema_version": true, "value": "quota_settlement_identity_v0", "modules": ["loopx/control_plane/effect_program.py", "loopx/control_plane/effect_program.ts"]}, - {"name": "SETTLEMENT_PLAN_SCHEMA_VERSION", "is_schema_version": true, "value": "quota_settlement_plan_v1", "modules": ["loopx/control_plane/effect_program.py", "loopx/control_plane/effect_program.ts"]}, - {"name": "SETTLEMENT_RECEIPT_SCHEMA_VERSION", "is_schema_version": true, "value": "quota_settlement_receipt_v1", "modules": ["loopx/control_plane/effect_program.py", "loopx/control_plane/effect_program.ts"]}, - {"name": "SHADOW_CAPTURE_PROFILE", "is_schema_version": false, "value": "file_outbox_v1", "modules": ["loopx/control_plane/coordination/shadow_management.py", "loopx/control_plane/coordination/shadow_management.ts"]}, - {"name": "SHADOW_MANAGEMENT_MANIFEST_SCHEMA", "is_schema_version": false, "value": "loopx_shadow_management_manifest_v1", "modules": ["loopx/control_plane/coordination/coordination_state_contract.generated.ts", "loopx/control_plane/coordination/coordination_state_contract_generated.py"]}, - {"name": "SHADOW_MANAGEMENT_STATE_SCHEMA", "is_schema_version": false, "value": "loopx_shadow_management_state_v1", "modules": ["loopx/control_plane/coordination/coordination_state_contract.generated.ts", "loopx/control_plane/coordination/coordination_state_contract_generated.py"]}, - {"name": "SHADOW_OUTBOX_MANIFEST_SCHEMA", "is_schema_version": false, "value": "loopx_shadow_outbox_manifest_v1", "modules": ["loopx/control_plane/coordination/coordination_state_contract.generated.ts", "loopx/control_plane/coordination/coordination_state_contract_generated.py"]}, - {"name": "SHARED_GOAL_ALIGNMENT_REQUEST_SCHEMA_VERSION", "is_schema_version": true, "value": "shared_goal_alignment_request_v0", "modules": ["loopx/control_plane/goals/shared_goal_alignment.py", "loopx/control_plane/goals/shared_goal_alignment.ts"]}, - {"name": "SHARED_GOAL_ALIGNMENT_SCHEMA_VERSION", "is_schema_version": true, "value": "shared_goal_alignment_v0", "modules": ["loopx/control_plane/goals/shared_goal_alignment.py", "loopx/control_plane/goals/shared_goal_alignment.ts"]}, - {"name": "TASK_LEASE_ACQUIRE_REQUEST_SCHEMA", "is_schema_version": false, "value": "loopx_task_lease_acquire_native_v0", "modules": ["loopx/control_plane/coordination/coordination_state_contract.generated.ts", "loopx/control_plane/coordination/coordination_state_contract_generated.py"]}, - {"name": "TASK_LEASE_CANONICAL_RENEW_REQUEST_SCHEMA", "is_schema_version": false, "value": "loopx_canonical_task_lease_renew_request_v0", "modules": ["loopx/control_plane/coordination/coordination_state_contract.generated.ts", "loopx/control_plane/coordination/coordination_state_contract_generated.py"]}, - {"name": "TASK_LEASE_LIFECYCLE_REQUEST_SCHEMA", "is_schema_version": false, "value": "loopx_task_lease_lifecycle_native_v0", "modules": ["loopx/control_plane/coordination/coordination_state_contract.generated.ts", "loopx/control_plane/coordination/coordination_state_contract_generated.py"]}, - {"name": "TASK_LEASE_SCHEMA_VERSION", "is_schema_version": true, "value": "task_lease_v0", "modules": ["loopx/control_plane/work_items/local_lease_record.py", "loopx/control_plane/work_items/task_lease_acquire.ts"]}, - {"name": "TODO_COMPLETION_FENCE_REQUEST_SCHEMA", "is_schema_version": false, "value": "loopx_todo_completion_fence_request_v0", "modules": ["loopx/control_plane/todos/completion_fence.py", "loopx/control_plane/todos/completion_fence.ts"]}, - {"name": "TODO_COMPLETION_FENCE_RESULT_SCHEMA", "is_schema_version": false, "value": "loopx_todo_completion_fence_result_v0", "modules": ["loopx/control_plane/todos/completion_fence.py", "loopx/control_plane/todos/completion_fence.ts"]}, - {"name": "TODO_COMPLETION_POLICY_FAILURE_SCHEMA", "is_schema_version": false, "value": "loopx_todo_completion_policy_failure_v0", "modules": ["loopx/control_plane/todos/completion_policy.py", "loopx/control_plane/todos/completion_transaction.ts"]}, - {"name": "TODO_COMPLETION_POLICY_REQUEST_SCHEMA", "is_schema_version": false, "value": "loopx_todo_completion_policy_request_v0", "modules": ["loopx/control_plane/todos/completion_policy.py", "loopx/control_plane/todos/completion_policy.ts"]}, - {"name": "TODO_COMPLETION_POLICY_RESULT_SCHEMA", "is_schema_version": false, "value": "loopx_todo_completion_policy_result_v0", "modules": ["loopx/control_plane/todos/completion_policy.py", "loopx/control_plane/todos/completion_policy.ts"]}, - {"name": "TODO_COMPLETION_STATE_REQUEST_SCHEMA", "is_schema_version": false, "value": "loopx_todo_completion_state_request_v0", "modules": ["loopx/control_plane/todos/completion_state.py", "loopx/control_plane/todos/completion_state.ts"]}, - {"name": "TODO_COMPLETION_STATE_RESULT_SCHEMA", "is_schema_version": false, "value": "loopx_todo_completion_state_result_v0", "modules": ["loopx/control_plane/todos/completion_state.py", "loopx/control_plane/todos/completion_state.ts"]}, - {"name": "TODO_COMPLETION_TRANSACTION_REQUEST_SCHEMA", "is_schema_version": false, "value": "loopx_todo_completion_transaction_v0", "modules": ["loopx/control_plane/todos/completion_transaction.py", "loopx/control_plane/todos/completion_transaction.ts"]}, - {"name": "TODO_COMPLETION_TRANSACTION_RESULT_SCHEMA", "is_schema_version": false, "value": "loopx_todo_completion_transaction_result_v0", "modules": ["loopx/control_plane/todos/completion_transaction.py", "loopx/control_plane/todos/completion_transaction.ts"]}, - {"name": "TODO_DECISION_SCOPE_SCHEMA_VERSION", "is_schema_version": true, "value": "decision_scope_v0", "modules": ["loopx/control_plane/todos/contract.py", "loopx/control_plane/todos/decision_metadata.ts"]}, - {"name": "TODO_NEXT_ACTION_REQUEST_SCHEMA", "is_schema_version": false, "value": "loopx_todo_next_action_transition_v1", "modules": ["loopx/control_plane/todos/next_action.ts", "loopx/control_plane/todos/next_action_runtime.py"]}, - {"name": "TODO_NEXT_ACTION_RESULT_SCHEMA", "is_schema_version": false, "value": "loopx_todo_next_action_result_v0", "modules": ["loopx/control_plane/todos/next_action.ts", "loopx/control_plane/todos/next_action_runtime.py"]}, - {"name": "TODO_PLANNING_INVENTORY_REQUEST_SCHEMA_VERSION", "is_schema_version": true, "value": "todo_planning_inventory_request_v0", "modules": ["loopx/control_plane/work_items/planning_inventory.py", "loopx/control_plane/work_items/planning_inventory.ts"]}, - {"name": "TODO_PRESENTATION_METADATA_SCHEMA", "is_schema_version": false, "value": "loopx_todo_presentation_metadata_v0", "modules": ["loopx/control_plane/coordination/todo_presentation.ts", "loopx/control_plane/todos/todo_semantics.py"]}, - {"name": "TODO_RESUME_EVALUATION_REQUEST_SCHEMA", "is_schema_version": false, "value": "todo_resume_evaluation_request_v0", "modules": ["loopx/control_plane/coordination/coordination_state_contract.generated.ts", "loopx/control_plane/coordination/coordination_state_contract_generated.py"]}, - {"name": "TODO_RESUME_EVALUATION_RESULT_SCHEMA", "is_schema_version": false, "value": "todo_resume_evaluation_v0", "modules": ["loopx/control_plane/coordination/coordination_state_contract.generated.ts", "loopx/control_plane/coordination/coordination_state_contract_generated.py"]}, - {"name": "TODO_RESUME_EXTERNAL_WAIT_REQUEST_SCHEMA", "is_schema_version": false, "value": "todo_external_wait_request_v0", "modules": ["loopx/control_plane/coordination/coordination_state_contract.generated.ts", "loopx/control_plane/coordination/coordination_state_contract_generated.py"]}, - {"name": "TODO_RESUME_EXTERNAL_WAIT_RESULT_SCHEMA", "is_schema_version": false, "value": "todo_external_wait_transition_v0", "modules": ["loopx/control_plane/coordination/coordination_state_contract.generated.ts", "loopx/control_plane/coordination/coordination_state_contract_generated.py"]}, - {"name": "TODO_RESUME_NORMALIZE_REQUEST_SCHEMA", "is_schema_version": false, "value": "todo_resume_normalize_request_v0", "modules": ["loopx/control_plane/coordination/coordination_state_contract.generated.ts", "loopx/control_plane/coordination/coordination_state_contract_generated.py"]}, - {"name": "TODO_SUCCESSOR_DERIVATION_REQUEST_SCHEMA", "is_schema_version": false, "value": "loopx_todo_successor_derivation_request_v0", "modules": ["loopx/control_plane/coordination/todo_successor_derivation.ts", "loopx/control_plane/todos/successor_derivation.py"]}, - {"name": "TODO_SUCCESSOR_DERIVATION_RESULT_SCHEMA", "is_schema_version": false, "value": "loopx_todo_successor_derivation_result_v0", "modules": ["loopx/control_plane/coordination/todo_successor_derivation.ts", "loopx/control_plane/todos/successor_derivation.py"]}, - {"name": "TURN_ENVELOPE_SCHEMA_VERSION", "is_schema_version": true, "value": "loopx_turn_envelope_v0", "modules": ["loopx/control_plane/quota/turn_envelope.py", "loopx/control_plane/quota/turn_envelope.ts"]}, - {"name": "TURN_JOURNAL_INSPECTION_SCHEMA_VERSION", "is_schema_version": true, "value": "loopx_turn_journal_inspection_v1", "modules": ["loopx/control_plane/turn_driver/turn_journal.ts", "loopx/control_plane/turn_driver/turn_journal_runtime.py"]}, - {"name": "TURN_SETTLEMENT_REDUCTION_SCHEMA_VERSION", "is_schema_version": true, "value": "loopx_turn_settlement_reduction_v0", "modules": ["loopx/control_plane/turn_driver/settlement.py", "loopx/control_plane/turn_driver/settlement.ts"]}, - {"name": "TURN_SETTLEMENT_TRANSACTION_SCHEMA_VERSION", "is_schema_version": true, "value": "loopx_turn_settlement_transaction_v0", "modules": ["loopx/control_plane/turn_driver/settlement.py", "loopx/control_plane/turn_driver/settlement.ts"]}, - {"name": "VISION_CHECKPOINT_SCHEMA_VERSION", "is_schema_version": true, "value": "vision_checkpoint_v0", "modules": ["loopx/control_plane/goals/vision_checkpoint.py", "loopx/control_plane/goals/vision_checkpoint.ts"]}, - {"name": "VISION_REFRESH_PREPARED_SCHEMA_VERSION", "is_schema_version": true, "value": "vision_refresh_prepared_v0", "modules": ["loopx/control_plane/goals/vision_checkpoint.py", "loopx/control_plane/goals/vision_checkpoint.ts"]}, - {"name": "VISION_REFRESH_REQUEST_SCHEMA", "is_schema_version": false, "value": "loopx_vision_refresh_request_v0", "modules": ["loopx/control_plane/goals/vision_checkpoint.py", "loopx/control_plane/goals/vision_checkpoint.ts"]} - ], - "same_runtime_forks": [ - {"name": "AGENT_LANE_PROGRESS_SCOPE", "is_schema_version": false, "value": "agent_lane", "modules": ["loopx/control_plane/agents/agent_lane_recommendation.py", "loopx/control_plane/status/run_projection.py", "loopx/history.py", "loopx/state_refresh.py", "loopx/status.py"]}, - {"name": "AUTONOMOUS_REPLAN_SCHEMA_VERSION", "is_schema_version": true, "value": "autonomous_replan_obligation_v0", "modules": ["loopx/control_plane/status/autonomous_replan_projection.py", "loopx/status.py"]}, - {"name": "CHAT_CAPABILITIES_PATH", "is_schema_version": false, "value": "/api/chat/capabilities", "modules": ["loopx/chat_server.py", "loopx/dashboard_launcher.py"]}, - {"name": "DEAD_MONITOR_REPEAT_SCHEMA_VERSION", "is_schema_version": true, "value": "dead_monitor_repeat_v0", "modules": ["loopx/control_plane/status/autonomous_replan_projection.py", "loopx/status.py"]}, - {"name": "DELIVERY_INTENT_SCHEMA", "is_schema_version": false, "value": "periodic_report_delivery_intent_v0", "modules": ["loopx/extensions/lark/miaoda_report.py", "loopx/extensions/lark/periodic_report_delivery.py"]}, - {"name": "MONITOR_DISPLAY_FALLBACK_ACTION", "is_schema_version": false, "value": "No immediate agent work; keep the monitor quiet until a material monitor transition, regression, or concrete blocker appears.", "modules": ["loopx/control_plane/status/monitor_display_projection.py", "loopx/status.py"]}, - {"name": "MONITOR_DISPLAY_SCHEMA_VERSION", "is_schema_version": true, "value": "monitor_quiet_display_v0", "modules": ["loopx/control_plane/status/monitor_display_projection.py", "loopx/status.py"]}, - {"name": "MONITOR_DISPLAY_STOP_CONDITION", "is_schema_version": false, "value": "stop until a material monitor transition, regression, or concrete blocker appears", "modules": ["loopx/control_plane/status/monitor_display_projection.py", "loopx/status.py"]}, - {"name": "MONITOR_SIGNAL_WAITING_ON", "is_schema_version": false, "value": "monitor_signal", "modules": ["loopx/control_plane/status/attention_projection.py", "loopx/control_plane/status/goal_attention_projection.py", "loopx/control_plane/status/monitor_display_projection.py", "loopx/status.py"]}, - {"name": "PLANNED_CONTROLLER_OPT_IN_RECOMMENDED_ACTION", "is_schema_version": false, "value": "先在 LoopX 完成 operator 判断;同意后项目 Agent 只执行 read-only map dry-run", "modules": ["loopx/control_plane/status/goal_attention_projection.py", "loopx/status.py"]}, - {"name": "QUALIFIED_NOKV_SDK_VERSION", "is_schema_version": false, "value": "0.11.0", "modules": ["loopx/control_plane/coordination/nokv_jsonl_helper.py", "loopx/control_plane/testing/authority_e2e_ladder.py"]}, - {"name": "RECEIPT_SCHEMA_VERSION", "is_schema_version": true, "value": "loopx_authority_receipt_v0", "modules": ["loopx/control_plane/coordination/executor.py", "loopx/control_plane/coordination/head.py"]}, - {"name": "RESPONSE_SCHEMA", "is_schema_version": false, "value": "semantic_preference_provider_response_v0", "modules": ["loopx/capabilities/semantic_preference/contract.py", "loopx/extensions/openviking_semantic_preference/provider.py"]}, - {"name": "SINK_VISIBILITY_OWNER_ONLY", "is_schema_version": false, "value": "owner-only", "modules": ["loopx/extensions/lark/presentation/explore_results.py", "loopx/extensions/lark/presentation/kanban.py"]}, - {"name": "SINK_VISIBILITY_SHARED", "is_schema_version": false, "value": "shared", "modules": ["loopx/extensions/lark/presentation/explore_results.py", "loopx/extensions/lark/presentation/kanban.py"]}, - {"name": "SKILLS_SUBDIR", "is_schema_version": false, "value": "skills", "modules": ["loopx/agy_goal_mode/__init__.py", "loopx/kiro_cli_goal_mode/__init__.py", "loopx/zcode_goal_mode/__init__.py"]}, - {"name": "SNAPSHOT_SCHEMA_VERSION", "is_schema_version": true, "value": "issue_fix_repository_reporting_snapshot_v0", "modules": ["loopx/capabilities/issue_fix/metrics_projection.py", "loopx/capabilities/issue_fix/repository_snapshot.py"]}, - {"name": "STATE_EVENT_LOG_BASENAME", "is_schema_version": false, "value": "events.jsonl", "modules": ["loopx/control_plane/status/active_state_projection.py", "loopx/status.py"]}, - {"name": "STATUS_CONTRACT_RELOAD_HINT", "is_schema_version": false, "value": "scripts/macos-dashboard-launchagent.sh restart", "modules": ["loopx/control_plane/status/contract_projection.py", "loopx/status.py"]}, - {"name": "SUPPLEMENT_SCHEMA_VERSION", "is_schema_version": true, "value": "issue_fix_metrics_supplement_v0", "modules": ["loopx/capabilities/issue_fix/metrics_projection.py", "loopx/capabilities/issue_fix/metrics_supplement.py"]}, - {"name": "TODO_ARCHIVE_STATE_ACTIVE", "is_schema_version": false, "value": "active", "modules": ["loopx/control_plane/todos/handoff_gate.py", "loopx/control_plane/todos/todo_summary.py"]}, - {"name": "TODO_TASK_CLASS_ADVANCEMENT", "is_schema_version": false, "value": "advancement_task", "modules": ["loopx/control_plane/goals/goal_frontier/__init__.py", "loopx/control_plane/goals/goal_frontier/long_todo_chain.py", "loopx/control_plane/todos/contract.py", "loopx/control_plane/todos/frontier_revision.py"]}, - {"name": "TODO_TASK_CLASS_MONITOR", "is_schema_version": false, "value": "continuous_monitor", "modules": ["loopx/control_plane/goals/goal_frontier/__init__.py", "loopx/control_plane/todos/contract.py"]}, - {"name": "TRIGGER_DECISION_SCHEMA", "is_schema_version": false, "value": "periodic_report_trigger_decision_v0", "modules": ["loopx/capabilities/periodic_report/core.py", "loopx/capabilities/periodic_report/triggers.py"]}, - {"name": "WORK_LANE_CONTRACT_SCHEMA_VERSION", "is_schema_version": true, "value": "work_lane_contract_v1", "modules": ["loopx/control_plane/work_items/capability_monitor_fallback.py", "loopx/control_plane/work_items/work_lane.py"]} - ], - "conflicting_values": [ - {"name": "BINDING_SCHEMA_VERSION", "is_schema_version": true, "definitions": [{"module": "loopx/control_plane/operator_inbox_binding.py", "value": "operator_inbox_binding_v0"}, {"module": "loopx/kunluncode_goal_mode/context.py", "value": "loopx_kunluncode_binding_v0"}]}, - {"name": "CAPABILITY_ID", "is_schema_version": false, "definitions": [{"module": "loopx/capabilities/periodic_report/pending_intent.py", "value": "periodic-report"}, {"module": "loopx/capabilities/reliability_diagnostics/envelope.py", "value": "reliability-diagnostics"}, {"module": "loopx/capabilities/repository_change_window/interaction_hook.py", "value": "repository-change-window"}]}, - {"name": "COMMAND", "is_schema_version": false, "definitions": [{"module": "loopx/capabilities/deep_research/runtime.py", "value": "/loopx-deepresearch"}, {"module": "loopx/global_risks.py", "value": "/loopx-global-risks"}, {"module": "loopx/global_todos.py", "value": "/loopx-global-todos"}, {"module": "loopx/pr_review.py", "value": "/loopx-pr-review"}, {"module": "loopx/summary_all.py", "value": "/loopx-global-summary"}]}, - {"name": "CONFIG_SCHEMA_VERSION", "is_schema_version": true, "definitions": [{"module": "loopx/extensions/lark/event_collector.py", "value": "lark_event_collector_config_v1"}, {"module": "loopx/extensions/lark/event_inbox.py", "value": "lark_event_inbox_config_v0"}]}, - {"name": "CURSOR_SCHEMA_VERSION", "is_schema_version": true, "definitions": [{"module": "loopx/extensions/lark/group_history_cursor.py", "value": "lark_group_history_cursor_v1"}, {"module": "loopx/extensions/lark/turn_start_sync.py", "value": "lark_turn_start_sync_cursor_v0"}]}, - {"name": "DECISION_CONTEXT_CAPABILITY_ID", "is_schema_version": false, "definitions": [{"module": "loopx/capabilities/decision_context/extension_provider.py", "value": "decision-context"}, {"module": "loopx/capabilities/decision_context/packets.py", "value": "decision_context"}]}, - {"name": "DEFAULT_AGENT_ID", "is_schema_version": false, "definitions": [{"module": "loopx/extensions/lark/presentation/kanban.py", "value": "codex-kanban-worker"}, {"module": "loopx/kunluncode_goal_mode/__init__.py", "value": "kunlun"}]}, - {"name": "EVENT_SCHEMA_VERSION", "is_schema_version": true, "definitions": [{"module": "loopx/capabilities/repository_change_window/ledger.py", "value": "repository_pending_change_event_v0"}, {"module": "loopx/extensions/external_connector_runtime.py", "value": "agent_external_connector_event_v0"}, {"module": "loopx/extensions/lark/event_inbox.py", "value": "lark_event_inbox_event_v0"}]}, - {"name": "HOOK_ID", "is_schema_version": false, "definitions": [{"module": "loopx/capabilities/periodic_report/pending_intent.py", "value": "periodic_report.pending_intent"}, {"module": "loopx/capabilities/repository_change_window/interaction_hook.py", "value": "repository_change_window.repository_delivery"}]}, - {"name": "MCP_REQUIREMENT", "is_schema_version": false, "definitions": [{"module": "loopx/claude_goal_mode/scripts/install.py", "value": "mcp<2"}, {"module": "loopx/kunluncode_goal_mode/cli.py", "value": "mcp==1.28.1"}]}, - {"name": "PLAN_SCHEMA_VERSION", "is_schema_version": true, "definitions": [{"module": "loopx/capabilities/integration_branch/core.py", "value": "loopx_integration_branch_plan_v0"}, {"module": "loopx/extensions/lark/event_collector.py", "value": "lark_event_collector_plan_v0"}]}, - {"name": "PROJECTION_SCHEMA_VERSION", "is_schema_version": true, "definitions": [{"module": "loopx/capabilities/issue_fix/metrics_projection.py", "value": "issue_fix_metrics_projection_v0"}, {"module": "loopx/capabilities/issue_fix/metrics_supplement.py", "value": "issue_fix_metrics_supplement_projection_v0"}]}, - {"name": "REQUEST_SCHEMA", "is_schema_version": false, "definitions": [{"module": "loopx/capabilities/periodic_report/core.py", "value": "periodic_report_run_request_v0"}, {"module": "loopx/capabilities/semantic_preference/contract.py", "value": "semantic_preference_provider_request_v0"}, {"module": "loopx/control_plane/handoff/review_batch.py", "value": "review_batch_request_v0"}, {"module": "loopx/extensions/openviking_periodic_report/provider.py", "value": "openviking_periodic_report_archive_request_v0"}, {"module": "loopx/extensions/openviking_semantic_preference/provider.py", "value": "semantic_preference_provider_request_v0"}]}, - {"name": "SCHEMA_VERSION", "is_schema_version": true, "definitions": [{"module": "loopx/agent_onboarding.py", "value": "loopx_agent_onboarding_v0"}, {"module": "loopx/bootstrap_command_pack.py", "value": "loopx_bootstrap_command_pack_v0"}, {"module": "loopx/capabilities/explore/source_history_reconcile.py", "value": "loopx_explore_source_history_reconcile_v0"}, {"module": "loopx/capabilities/pr_review_queue/merge_readiness.py", "value": "pull_request_merge_readiness_v0"}, {"module": "loopx/capabilities/public_safe_outbound/scanner.py", "value": "public_safe_outbound_scan_v0"}, {"module": "loopx/control_plane/runtime/agent_scoped_evidence_log.py", "value": "agent_scoped_evidence_log_v0"}, {"module": "loopx/extensions/lark/manager_reply_delivery.py", "value": "lark_manager_reply_delivery_v0"}, {"module": "loopx/global_risks.py", "value": "global_manager_command_response_v0"}, {"module": "loopx/global_todos.py", "value": "global_manager_command_response_v0"}, {"module": "loopx/host_loop_activation.py", "value": "loopx_host_loop_activation_v1"}, {"module": "loopx/host_mode_planner.py", "value": "host_mode_plan_v0"}, {"module": "loopx/pr_review.py", "value": "loopx_pr_review_command_response_v0"}, {"module": "loopx/presentation/projection_source_reconcile.py", "value": "projection_source_reconcile_plan_v0"}, {"module": "loopx/slash_command_install.py", "value": "loopx_slash_command_install_v0"}, {"module": "loopx/slash_commands.py", "value": "loopx_slash_command_catalog_v0"}, {"module": "loopx/summary_all.py", "value": "global_manager_command_response_v0"}]}, - {"name": "SKILLS_ROOT_LABEL", "is_schema_version": false, "definitions": [{"module": "loopx/agy_goal_mode/__init__.py", "value": "~/.gemini/antigravity-cli/skills"}, {"module": "loopx/kiro_cli_goal_mode/__init__.py", "value": "KIRO_HOME/skills"}, {"module": "loopx/zcode_goal_mode/__init__.py", "value": "ZCODE_HOME/skills"}]}, - {"name": "STATUS_SCHEMA_VERSION", "is_schema_version": true, "definitions": [{"module": "loopx/capabilities/integration_branch/core.py", "value": "loopx_integration_branch_status_v0"}, {"module": "loopx/extensions/lark/event_collector.py", "value": "lark_event_collector_status_v0"}]}, - {"name": "SURFACE", "is_schema_version": false, "definitions": [{"module": "loopx/capabilities/issue_fix/pr_description.py", "value": "issue_fix.pr_description"}, {"module": "loopx/capabilities/reward_memory/outbound.py", "value": "outbound_message.before_send"}]}, - {"name": "SYNC_SCHEMA_VERSION", "is_schema_version": true, "definitions": [{"module": "loopx/capabilities/integration_branch/core.py", "value": "loopx_integration_branch_sync_v0"}, {"module": "loopx/extensions/lark/turn_start_sync.py", "value": "lark_turn_start_inbox_sync_v0"}]} - ], - "multi_value_twins": [ - {"name": "AUTONOMOUS_RUN_HISTORY_NEUTRAL_CLASSIFICATIONS", "definitions": [{"kind": "python_closed_set", "module": "loopx/control_plane/status/autonomous_replan_projection.py", "values": ["quota_slot_spent", "quota_slot_voided", "delivery_completion_spend_accounted_v0"]}, {"kind": "python_closed_set", "module": "loopx/status.py", "values": ["quota_slot_spent", "quota_slot_voided", "delivery_completion_spend_accounted_v0"]}], "values": ["quota_slot_spent", "quota_slot_voided", "delivery_completion_spend_accounted_v0"], "modules": ["loopx/control_plane/status/autonomous_replan_projection.py", "loopx/status.py"]}, - {"name": "BACKLOG_HYGIENE_SECTION_HEADINGS", "definitions": [{"kind": "python_closed_set", "module": "loopx/control_plane/status/active_state_projection.py", "values": ["Next Action", "Operating Lessons"]}, {"kind": "python_closed_set", "module": "loopx/status.py", "values": ["Next Action", "Operating Lessons"]}], "values": ["Next Action", "Operating Lessons"], "modules": ["loopx/control_plane/status/active_state_projection.py", "loopx/status.py"]}, - {"name": "BRANCH_REPLAN_MERGE_STATES", "definitions": [{"kind": "python_closed_set", "module": "loopx/capabilities/issue_fix/outcome_projection.py", "values": ["BEHIND", "DIRTY"]}, {"kind": "python_closed_set", "module": "loopx/capabilities/issue_fix/pr_lifecycle.py", "values": ["BEHIND", "DIRTY"]}], "values": ["BEHIND", "DIRTY"], "modules": ["loopx/capabilities/issue_fix/outcome_projection.py", "loopx/capabilities/issue_fix/pr_lifecycle.py"]}, - {"name": "CAPTURE_SCOPES", "definitions": [{"kind": "python_closed_set", "module": "loopx/control_plane/work_items/operator_inbox.py", "values": ["addressed_only", "configured_chat_all"]}, {"kind": "python_closed_set", "module": "loopx/extensions/lark/event_inbox.py", "values": ["addressed_only", "configured_chat_all"]}], "values": ["addressed_only", "configured_chat_all"], "modules": ["loopx/control_plane/work_items/operator_inbox.py", "loopx/extensions/lark/event_inbox.py"]}, - {"name": "CONNECTED_ADAPTER_STATUSES", "definitions": [{"kind": "python_closed_set", "module": "loopx/control_plane/status/goal_attention_projection.py", "values": ["connected", "connected-read-only", "pre-tick-runnable"]}, {"kind": "python_closed_set", "module": "loopx/control_plane/status/lifecycle_projection.py", "values": ["connected", "connected-read-only", "pre-tick-runnable"]}, {"kind": "python_closed_set", "module": "loopx/status.py", "values": ["connected", "connected-read-only", "pre-tick-runnable"]}], "values": ["connected", "connected-read-only", "pre-tick-runnable"], "modules": ["loopx/control_plane/status/goal_attention_projection.py", "loopx/control_plane/status/lifecycle_projection.py", "loopx/status.py"]}, - {"name": "DELIVERY_CONTINUITY_PREEMPTIONS", "definitions": [{"kind": "python_closed_set", "module": "loopx/control_plane/turn_driver/delivery_continuity.py", "values": ["heartbeat_receipt", "blocking_work_lane", "autonomous_replan", "control_repair", "delivery_not_allowed"]}, {"kind": "typescript_const_array", "module": "loopx/control_plane/turn_driver/delivery_continuity.ts", "values": ["heartbeat_receipt", "blocking_work_lane", "autonomous_replan", "control_repair", "delivery_not_allowed"]}], "values": ["heartbeat_receipt", "blocking_work_lane", "autonomous_replan", "control_repair", "delivery_not_allowed"], "modules": ["loopx/control_plane/turn_driver/delivery_continuity.py", "loopx/control_plane/turn_driver/delivery_continuity.ts"]}, - {"name": "DELIVERY_WORKSPACE_IDENTITY_KINDS", "definitions": [{"kind": "python_closed_set", "module": "loopx/control_plane/agents/delivery_workspace.py", "values": ["git_repository", "local_goal"]}, {"kind": "typescript_const_array", "module": "loopx/control_plane/agents/delivery_workspace.ts", "values": ["git_repository", "local_goal"]}], "values": ["git_repository", "local_goal"], "modules": ["loopx/control_plane/agents/delivery_workspace.py", "loopx/control_plane/agents/delivery_workspace.ts"]}, - {"name": "DELIVERY_WORKSPACE_KINDS", "definitions": [{"kind": "python_closed_set", "module": "loopx/control_plane/agents/delivery_workspace.py", "values": ["canonical_checkout", "independent_git_worktree", "local_goal_workspace"]}, {"kind": "typescript_const_array", "module": "loopx/control_plane/agents/delivery_workspace.ts", "values": ["canonical_checkout", "independent_git_worktree", "local_goal_workspace"]}], "values": ["canonical_checkout", "independent_git_worktree", "local_goal_workspace"], "modules": ["loopx/control_plane/agents/delivery_workspace.py", "loopx/control_plane/agents/delivery_workspace.ts"]}, - {"name": "DELIVERY_WORKSPACE_REQUIREMENTS", "definitions": [{"kind": "python_closed_set", "module": "loopx/control_plane/quota/settlement_workspace_causality.py", "values": ["required", "not_required", "unknown"]}, {"kind": "typescript_const_array", "module": "loopx/control_plane/quota/settlement_workspace_causality.ts", "values": ["required", "not_required", "unknown"]}], "values": ["required", "not_required", "unknown"], "modules": ["loopx/control_plane/quota/settlement_workspace_causality.py", "loopx/control_plane/quota/settlement_workspace_causality.ts"]}, - {"name": "GOAL_AMENDMENT_CLASSES", "definitions": [{"kind": "python_closed_set", "module": "loopx/control_plane/goals/goal_amendment_proposal.py", "values": ["lane_route", "shared_work_graph", "shared_acceptance", "protected_authority"]}, {"kind": "typescript_const_array", "module": "loopx/control_plane/goals/goal_amendment_proposal.ts", "values": ["lane_route", "shared_work_graph", "shared_acceptance", "protected_authority"]}], "values": ["lane_route", "shared_work_graph", "shared_acceptance", "protected_authority"], "modules": ["loopx/control_plane/goals/goal_amendment_proposal.py", "loopx/control_plane/goals/goal_amendment_proposal.ts"]}, - {"name": "GOAL_AMENDMENT_PROPOSAL_ADMISSIONS", "definitions": [{"kind": "python_closed_set", "module": "loopx/control_plane/goals/goal_amendment_proposal.py", "values": ["admitted", "needs_rebase"]}, {"kind": "typescript_const_array", "module": "loopx/control_plane/goals/goal_amendment_proposal.ts", "values": ["admitted", "needs_rebase"]}], "values": ["admitted", "needs_rebase"], "modules": ["loopx/control_plane/goals/goal_amendment_proposal.py", "loopx/control_plane/goals/goal_amendment_proposal.ts"]}, - {"name": "GOAL_AMENDMENT_PROPOSAL_ADMISSION_FACTS", "definitions": [{"kind": "python_closed_set", "module": "loopx/control_plane/goals/goal_amendment_proposal.py", "values": ["base_state_event_basis_sequence_behind_derived_head", "base_source_basis_digest_mismatch", "base_source_basis_unverifiable", "base_revision_basis_superseded"]}, {"kind": "typescript_const_array", "module": "loopx/control_plane/goals/goal_amendment_proposal.ts", "values": ["base_state_event_basis_sequence_behind_derived_head", "base_source_basis_digest_mismatch", "base_source_basis_unverifiable", "base_revision_basis_superseded"]}], "values": ["base_state_event_basis_sequence_behind_derived_head", "base_source_basis_digest_mismatch", "base_source_basis_unverifiable", "base_revision_basis_superseded"], "modules": ["loopx/control_plane/goals/goal_amendment_proposal.py", "loopx/control_plane/goals/goal_amendment_proposal.ts"]}, - {"name": "LEGACY_EXTERNAL_EVIDENCE_CLASSIFICATION_PREFIXES", "definitions": [{"kind": "python_closed_set", "module": "loopx/control_plane/status/goal_attention_projection.py", "values": ["await_", "external_evidence_observation_"]}, {"kind": "python_closed_set", "module": "loopx/status.py", "values": ["await_", "external_evidence_observation_"]}], "values": ["await_", "external_evidence_observation_"], "modules": ["loopx/control_plane/status/goal_attention_projection.py", "loopx/status.py"]}, - {"name": "LIFECYCLE_PRIORITY", "definitions": [{"kind": "python_closed_set", "module": "loopx/control_plane/status/lifecycle_projection.py", "values": ["controller_ready", "reward_judged", "operator_approved", "controller_gated", "operator_gated", "adapter_inspected", "mapped", "refreshed", "connected", "registered", "planned", "run_recorded"]}, {"kind": "python_closed_set", "module": "loopx/status.py", "values": ["controller_ready", "reward_judged", "operator_approved", "controller_gated", "operator_gated", "adapter_inspected", "mapped", "refreshed", "connected", "registered", "planned", "run_recorded"]}], "values": ["controller_ready", "reward_judged", "operator_approved", "controller_gated", "operator_gated", "adapter_inspected", "mapped", "refreshed", "connected", "registered", "planned", "run_recorded"], "modules": ["loopx/control_plane/status/lifecycle_projection.py", "loopx/status.py"]}, - {"name": "REGISTRY_DIRS", "definitions": [{"kind": "python_closed_set", "module": "loopx/claude_goal_mode/hooks/goal_state.py", "values": [".loopx", ".goal-harness"]}, {"kind": "python_closed_set", "module": "loopx/goal_mode_context.py", "values": [".loopx", ".goal-harness"]}], "values": [".loopx", ".goal-harness"], "modules": ["loopx/claude_goal_mode/hooks/goal_state.py", "loopx/goal_mode_context.py"]}, - {"name": "REGISTRY_WAITING_ON_OVERRIDES", "definitions": [{"kind": "python_closed_set", "module": "loopx/control_plane/status/goal_attention_projection.py", "values": ["user_or_controller", "controller", "codex", "external_evidence"]}, {"kind": "python_closed_set", "module": "loopx/status.py", "values": ["user_or_controller", "controller", "codex", "external_evidence"]}], "values": ["user_or_controller", "controller", "codex", "external_evidence"], "modules": ["loopx/control_plane/status/goal_attention_projection.py", "loopx/status.py"]}, - {"name": "SOURCE_REGISTRY_SHADOW_FINDINGS", "definitions": [{"kind": "python_closed_set", "module": "loopx/control_plane/status/registry_health_projection.py", "values": ["source_registry_missing", "stale_source_registry"]}, {"kind": "python_closed_set", "module": "loopx/status.py", "values": ["source_registry_missing", "stale_source_registry"]}], "values": ["source_registry_missing", "stale_source_registry"], "modules": ["loopx/control_plane/status/registry_health_projection.py", "loopx/status.py"]}, - {"name": "SUPPORT_ASPECTS", "definitions": [{"kind": "python_closed_set", "module": "loopx/capabilities/issue_fix/repository_context.py", "values": ["architecture", "ownership", "change_scope", "reproduction", "validation"]}, {"kind": "python_closed_set", "module": "loopx/capabilities/issue_fix/repository_memory.py", "values": ["architecture", "ownership", "change_scope", "reproduction", "validation"]}], "values": ["architecture", "ownership", "change_scope", "reproduction", "validation"], "modules": ["loopx/capabilities/issue_fix/repository_context.py", "loopx/capabilities/issue_fix/repository_memory.py"]}, - {"name": "TERMINAL_PR_STATES", "definitions": [{"kind": "python_closed_set", "module": "loopx/capabilities/issue_fix/pr_gate_reconcile.py", "values": ["MERGED", "CLOSED"]}, {"kind": "python_closed_set", "module": "loopx/capabilities/issue_fix/pr_lifecycle.py", "values": ["MERGED", "CLOSED"]}], "values": ["MERGED", "CLOSED"], "modules": ["loopx/capabilities/issue_fix/pr_gate_reconcile.py", "loopx/capabilities/issue_fix/pr_lifecycle.py"]} - ], - "multi_value_forks": [ - {"name": "AGENT_TODO_HEADER_MARKERS", "definitions": [{"kind": "python_closed_set", "module": "loopx/control_plane/goals/active_state_metadata.py", "values": ["agent todo", "codex todo", "project agent todo"]}, {"kind": "python_closed_set", "module": "loopx/state_projection.py", "values": ["agent todo", "agent backlog", "agent action", "项目 agent", "agent 待办"]}]}, - {"name": "RAW_MATERIAL_KEY_HINTS", "definitions": [{"kind": "python_closed_set", "module": "loopx/capabilities/content_ops/surface.py", "values": ["body", "chat", "credential", "dm", "local_path", "log", "message", "raw", "secret", "token", "transcript"]}, {"kind": "python_closed_set", "module": "loopx/control_plane/goals/goal_channel_projection.py", "values": ["credential", "local_path", "log", "raw", "secret", "stderr", "stdout", "token", "trace", "transcript"]}]}, - {"name": "SOURCE_SURFACES", "definitions": [{"kind": "python_closed_set", "module": "loopx/global_risks.py", "values": ["status contract diagnostics", "global registry health findings", "status attention queue stale-run warnings", "status compact run-history coordination"]}, {"kind": "python_closed_set", "module": "loopx/global_todos.py", "values": ["status attention queue", "quota should-run summaries", "active-state todo projections", "compact run-history projection consumed by status"]}, {"kind": "python_closed_set", "module": "loopx/pr_review.py", "values": ["GitHub pull request metadata", "GitHub pull request body summary", "GitHub pull request changed-file list", "GitHub pull request status check rollup"]}, {"kind": "python_closed_set", "module": "loopx/summary_all.py", "values": ["global registry compact status", "status attention queue", "quota should-run summaries", "active-state todo projections", "run history index summaries"]}]}, - {"name": "USER_TODO_HEADER_MARKERS", "definitions": [{"kind": "python_closed_set", "module": "loopx/control_plane/goals/active_state_metadata.py", "values": ["user todo", "owner review reading queue", "owner reading queue"]}, {"kind": "python_closed_set", "module": "loopx/state_projection.py", "values": ["user todo", "owner review", "owner todo", "user action", "用户", "人工", "owner"]}]} - ] - }, - "summary": { - "source_files": 1183, - "python_enums": 103, - "python_closed_sets": 494, - "python_literal_aliases": 8, - "typescript_const_arrays": 40, - "named_string_constants": 2075, - "schema_version_names": 756, - "schema_version_same_runtime_forks": 7, - "cross_runtime_twins": 166, - "same_runtime_forks": 25, - "same_runtime_fork_definitions": 58, - "conflicting_values": 18, - "conflicting_definitions": 59, - "multi_value_twins": 19, - "multi_value_forks": 4, - "multi_value_fork_definitions": 10, - "same_runtime_forks_semantic": 18, - "conflicting_values_semantic": 2 - } -} diff --git a/loopx/semantics/production.py b/loopx/semantics/production.py new file mode 100644 index 0000000000..45cee142d2 --- /dev/null +++ b/loopx/semantics/production.py @@ -0,0 +1,219 @@ +"""Collect and check bounded semantic production evidence for repository CI.""" +from __future__ import annotations + +import ast +import json +from pathlib import Path +import subprocess +from typing import Any + +from .inventory import SourceFile +from .python_production import Production, enum_members, scan_python_production + + +# This source boundary is code owned. It is not adjustable through registry data. +PRODUCER_ROOTS = ( + 'loopx/cli_commands', 'loopx/control_plane/agents', + 'loopx/control_plane/quota', 'loopx/control_plane/todos', 'loopx/control_plane/coordination', + 'loopx/control_plane/turn_driver', 'loopx/control_plane/work_items', +) +PRODUCER_FILES = frozenset({ + 'loopx/control_plane/effect_program.py', 'loopx/control_plane/effect_program.ts', +}) + +# Root should-run actions preserve two disjoint, independently owned domains. +# Registry data cannot add another union arm to weaken field closedness. +QUOTA_ACTION_VOCABULARIES = ('effective_action', 'agent_scope_frontier_action') + + +def quota_action_domain(registry: dict[str, Any]) -> set[str]: + slots = [slot for entry in registry['relations']['shared_field_names'] + if entry.get('field') == 'effective_action' + for slot in entry['slots'] if slot.get('slot') == 'should_run.effective_action'] + if len(slots) != 1 or slots[0].get('vocabularies') != list(QUOTA_ACTION_VOCABULARIES): + raise ValueError('should_run.effective_action must retain its anchored decision/frontier union') + result: set[str] = set() + for name in QUOTA_ACTION_VOCABULARIES: + values = set(registry['vocabularies'][name]['values']) + if result & values: + raise ValueError('should_run.effective_action union arms must be disjoint') + result.update(values) + return result + + +def collect_production(root: Path, vocabulary: dict[str, Any], sources: list[SourceFile]) -> list[Production]: + by_path = {s.path: s for s in sources} + owner = vocabulary['owners'].get('python') + enums = {} + if owner: + module, symbol = owner.split('::') + if module not in by_path: + raise ValueError(f'producer owner must be a tracked source: {owner}') + enums[owner] = enum_members(by_path[module], symbol) + field = vocabulary.get('literal_scan', {}).get('field') + returns = vocabulary.get('return_producers', []) + return_paths = vocabulary.get('return_paths', {}) + if not set(return_paths) <= set(returns): + raise ValueError('return paths must name declared return producers') + if any(not isinstance(path, list) or not path or any(type(key) not in (str, int) for key in path) + for path in return_paths.values()): + raise ValueError('return paths must be nonempty literal field/index paths') + calls = _call_arguments(vocabulary.get('call_producers', {}), by_path) + selected = [s for s in sources if s.path in PRODUCER_FILES + or any(s.path.startswith(p + '/') for p in PRODUCER_ROOTS)] + rows = [] + for source in selected: + if source.suffix == '.py': + names = frozenset(site.split('::')[1] for site in returns if site.split('::')[0] == source.path) + paths = {site.split('::')[1]: tuple(path) for site, path in return_paths.items() + if site.split('::')[0] == source.path} + rows.extend(scan_python_production(source, field=field, enums=enums, return_functions=names, + return_paths=paths, call_arguments=calls)) + rows.extend(_typescript_scan(root, [s for s in selected if s.suffix == '.ts'], field, returns)) + return rows + + +def _call_arguments(declarations: dict[str, list[str]], sources: dict[str, SourceFile]) -> dict[str, dict[str, int | None]]: + """Bind reviewed output-builder parameters to their actual source signature. + + This is a finite caller contract, not interprocedural inference. The registry + names the output argument; tracked source proves the target/signature, and + the Python scanner proves an unshadowed local or imported call binding. + """ + calls = {} + for site, names in declarations.items(): + path, symbol = site.split('::') + if path not in sources or sources[path].suffix != '.py': + raise ValueError(f'call producer must name a tracked Python builder: {site}') + functions = [n for n in ast.parse(sources[path].text, filename=path).body + if isinstance(n, (ast.FunctionDef, ast.AsyncFunctionDef)) and n.name == symbol] + if len(functions) != 1: + raise ValueError(f'call producer requires one top-level builder: {site}') + args = functions[0].args + parameters: dict[str, int | None] = {arg.arg: i for i, arg in enumerate([*args.posonlyargs, *args.args])} + parameters.update({arg.arg: None for arg in args.kwonlyargs}) + if not names or len(set(names)) != len(names) or not set(names) <= parameters.keys(): + raise ValueError(f'call producer arguments must match the builder signature: {site}') + calls[site] = {name: parameters[name] for name in names} + return calls + + +def _typescript_scan( + root: Path, ts_sources: list[SourceFile], field: str | None, + returns: list[str], mode: str = 'production', +) -> list[Production]: + if not ts_sources or not field: + return [] + rows = [] + completed = subprocess.run( + ['node', str(root / 'scripts/semantic_production_scan.mjs')], + input=json.dumps({'field': field, 'sources': [{'path': s.path, 'text': s.text} for s in ts_sources], + 'return_functions': returns, 'mode': mode}), + capture_output=True, text=True, encoding="utf-8", timeout=60, check=False, + ) + if completed.returncode: + # Accept only a bounded location from the parser, never echo source + # text or arbitrary subprocess stderr into public diagnostics. + try: + failure = json.loads(completed.stdout) + except json.JSONDecodeError: + failure = None + error = failure.get('error') if isinstance(failure, dict) else None + if (isinstance(error, dict) and error.get('code') == 'typescript_syntax' + and error.get('path') in {s.path for s in ts_sources} + and isinstance(error.get('line'), int) and error['line'] > 0): + raise ValueError(f"{error['path']}:{error['line']}: invalid TypeScript source; repair syntax before semantic scanning") + raise ValueError('TypeScript production parser failed; run npm ci --ignore-scripts and check the Node runtime') + rows.extend(Production(r['site'], r['line'], r['form'], frozenset(r['values']), r['unresolved']) + for r in json.loads(completed.stdout)) + return rows + + +def collect_literal_uses(root: Path, field: str, sources: list[SourceFile]) -> dict[str, set[str]]: + """Observe literal field writes/dispatch, with a deliberately bounded grammar.""" + from .python_production import python_literal_uses + + observed: dict[str, set[str]] = {} + for source in sources: + if source.suffix == '.py': + for value in python_literal_uses(source, field): + observed.setdefault(value, set()).add(source.path) + for row in _typescript_scan(root, [s for s in sources if s.suffix == '.ts'], field, [], 'literal_uses'): + for value in row.values: + observed.setdefault(value, set()).add(row.site.split('::')[0]) + return observed + + +def validate_production( + name: str, vocabulary: dict[str, Any], rows: list[Production], + *, field_domain: set[str] | None = None, +) -> list[str]: + """F1/F2 checks on observed results; owner members do not establish liveness. + + Unresolved sites are returned explicitly. Their unknown portion supplies + no value evidence; known output alternatives still count for closedness + and liveness. This function does not claim whole-program closedness. + """ + outputs = [row for row in rows if row.form != 'keyword_unproved'] + observed = set().union(*(row.values for row in outputs)) + expected = set(vocabulary['values']) + # A composed field domain never widens a canonical decision function's + # return type, and union arms cannot supply the owner's liveness evidence. + writes = {'assignment', 'dict', 'keyword', 'keyword_unproved', 'object'} + unregistered = set().union(*( + row.values - (field_domain if field_domain is not None and row.form in writes else expected) + for row in rows + )) + if unregistered: + sites = sorted({f'{r.site}:{r.line}' for r in rows if r.values & unregistered}) + role = 'producer writes' if any(r.values & unregistered for r in outputs) else 'field argument carries' + raise ValueError(f'{name}: {role} unregistered values {sorted(unregistered)} at {sites}') + compatibility = set(vocabulary.get('compatibility_only', {})) + if compatibility - expected: + raise ValueError(f'{name}: compatibility-only values must be registered') + if compatibility & observed: + raise ValueError(f'{name}: compatibility-only values are produced: {sorted(compatibility & observed)}') + missing = expected - observed - compatibility + if missing: + raise ValueError(f'{name}: values have no observed producer: {sorted(missing)}; owner definition is not production') + producers = vocabulary.get('producers', []) + declared = set(producers) + if len(declared) != len(producers): + raise ValueError(f'{name}: producer sites repeat') + returns = set(vocabulary.get('return_producers', [])) + if not returns <= declared: + raise ValueError(f'{name}: return producers must also be registered producers') + stale = sorted(declared - {row.site for row in outputs}) + if stale: + raise ValueError(f'{name}: producer sites have no observed write or return: {stale}') + undeclared = sorted({row.site for row in outputs if row.values and row.site not in declared}) + if undeclared: + raise ValueError(f'{name}: undeclared producer sites: {undeclared}') + return sorted({f'{row.site}:{row.line}' for row in rows if row.unresolved}) + + +def probe_turn_result_input_domain(vocabulary: dict[str, Any]) -> list[Production]: + """Witness this real decoder's finite output domain, not the host's traces. + + A successful call is evidence that the production function can emit a value + for a legal input. Merely enumerating the owner is not such evidence. The + callable is fixed in code; registry data cannot select arbitrary imports. + """ + from loopx.control_plane.turn_driver.transaction import LoopXTurnResultKind, _result_kind + + site = 'loopx/control_plane/turn_driver/transaction.py::_result_kind' + if vocabulary.get('input_producer') != site: + raise ValueError('turn_result_kind: input_producer must name the anchored decoder') + rows = [] + for value in vocabulary['values']: + errors: list[str] = [] + actual = _result_kind(value, errors) + if errors or not isinstance(actual, LoopXTurnResultKind) or actual.value != value: + raise ValueError(f'turn_result_kind: decoder does not produce registered input {value}') + rows.append(Production(site, _result_kind.__code__.co_firstlineno, 'input_witness', frozenset({actual.value}), False)) + for invalid in (None, '', 'unknown_result_kind', 3, [], {}): + errors = [] + actual = _result_kind(invalid, errors) + if actual is not None or not errors: + raise ValueError('turn_result_kind: decoder accepted an invalid input probe') + return rows diff --git a/loopx/semantics/python_production.py b/loopx/semantics/python_production.py new file mode 100644 index 0000000000..595e502249 --- /dev/null +++ b/loopx/semantics/python_production.py @@ -0,0 +1,436 @@ +"""Bounded Python production evidence; never execute inspected source. + +Known values are syntactic result possibilities, not proof of reachable traces. +Unresolved expressions retain their source locations. Owner definitions alone, +comparison operands, comments and quoted examples are not production evidence. +""" +from __future__ import annotations + +import ast +from collections import Counter +from dataclasses import dataclass +from typing import Mapping, TypeVar + +from .inventory import SourceFile + + +@dataclass(frozen=True) +class Production: + site: str + line: int + form: str + values: frozenset[str] + unresolved: bool + + +def _module(path: str) -> str: + name = path.removesuffix('.py').replace('/', '.') + return name.removesuffix('.__init__') + + +def _import_module(path: str, node: ast.ImportFrom) -> str: + if not node.level: + return node.module or '' + package = _module(path) if path.endswith('/__init__.py') else _module(path).rpartition('.')[0] + parts = package.split('.') + return '.'.join(parts[:len(parts) - node.level + 1] + ([node.module] if node.module else [])) + + +def enum_members(source: SourceFile, symbol: str, *, strict: bool = False) -> dict[str, str]: + """Extract literal members, with fail-closed generation as an explicit mode. + + Inventory/observation may inspect a bounded subset. Generation must account + for every declaration without executing source or inferring enum aliases + from iteration (which omits aliases present in ``__members__``). + """ + tree = ast.parse(source.text, filename=source.path) + classes = [n for n in tree.body if isinstance(n, ast.ClassDef) and n.name == symbol] + if len(classes) != 1: + raise ValueError(f'{source.path}::{symbol}: expected one owner class') + result: dict[str, str] = {} + for node in classes[0].body: + target = None + value = None + if isinstance(node, ast.Assign) and len(node.targets) == 1: + target = node.targets[0] + value = node.value + elif isinstance(node, ast.AnnAssign): + target = node.target + value = node.value + if strict: + if isinstance(node, ast.Pass) or (isinstance(node, ast.Expr) + and isinstance(node.value, ast.Constant) and isinstance(node.value.value, str)): + continue + names = [n.id for n in ast.walk(node) if isinstance(n, ast.Name) and isinstance(n.ctx, ast.Store)] + name = target.id if isinstance(target, ast.Name) else ','.join(names) or getattr(node, 'name', symbol) + prefix = f'{source.path}:{node.lineno}: member {name}' + if (not isinstance(target, ast.Name) or target.id.startswith('__') + or (target.id.startswith('_') and target.id.endswith('_'))): + raise ValueError(f'{prefix}: unsupported owner declaration') + if target.id in result: + raise ValueError(f'{prefix}: duplicate member declaration') + if not isinstance(value, ast.Constant) or not isinstance(value.value, str): + raise ValueError(f'{prefix}: expected a literal string; computed members and aliases are unsupported') + if value.value in result.values(): + original = next(k for k, v in result.items() if v == value.value) + raise ValueError(f'{prefix}: aliases member {original}; owner and registry differ (aliases unsupported)') + if (isinstance(target, ast.Name) and isinstance(value, ast.Constant) + and isinstance(value.value, str)): + result[target.id] = value.value + return result + + +def python_literal_uses(source: SourceFile, field: str) -> set[str]: + """Literal writes and direct dispatch operands; not alias/data-flow proof.""" + tree = ast.parse(source.text, filename=source.path) + + def reads(node: ast.AST) -> bool: + if isinstance(node, ast.Name): + return node.id == field + if isinstance(node, ast.Attribute): + return node.attr == field + if isinstance(node, ast.Subscript): + return isinstance(node.slice, ast.Constant) and node.slice.value == field + if isinstance(node, ast.BoolOp): + # Preserve the common neutral fallback without attributing a + # different field selected by and/or to this action slot. + return (isinstance(node.op, ast.Or) and reads(node.values[0]) + and all(isinstance(value, ast.Constant) and value.value in ('', None) + for value in node.values[1:])) + if isinstance(node, ast.Call): + if isinstance(node.func, ast.Name) and node.func.id == 'str' and len(node.args) == 1: + return reads(node.args[0]) + return (isinstance(node.func, ast.Attribute) and node.func.attr == 'get' + and bool(node.args) and isinstance(node.args[0], ast.Constant) + and node.args[0].value == field) + return False + + def literals(node: ast.AST) -> set[str]: + if isinstance(node, ast.Constant): + return {node.value} if isinstance(node.value, str) and node.value else set() + if isinstance(node, (ast.Tuple, ast.List, ast.Set)): + return set().union(*(literals(item) for item in node.elts)) + if isinstance(node, ast.MatchValue): + return literals(node.value) + if isinstance(node, ast.MatchOr): + return set().union(*(literals(pattern) for pattern in node.patterns)) + return set() + + # Production collection already separates conditional results from their + # conditions and does not inspect strings containing sample source text. + rows = scan_python_production(source, field=field, enums={}) + result = set().union(*(row.values for row in rows)) + for node in ast.walk(tree): + if isinstance(node, ast.Compare): + for left, operator, right in zip( + [node.left, *node.comparators[:-1]], node.ops, node.comparators, strict=True, + ): + if isinstance(operator, (ast.Eq, ast.NotEq, ast.Is, ast.IsNot, ast.In, ast.NotIn)) and reads(left): + result.update(literals(right)) + if isinstance(operator, (ast.Eq, ast.NotEq, ast.Is, ast.IsNot)) and reads(right): + result.update(literals(left)) + elif isinstance(node, ast.Match) and reads(node.subject): + for case in node.cases: + result.update(literals(case.pattern)) + return result + + +_Binding = TypeVar('_Binding') + + +def _qualified_bindings(source: SourceFile, tree: ast.Module, owners: Mapping[str, _Binding]) -> dict[str, _Binding]: + bindings = {owner.split('::')[1]: value for owner, value in owners.items() + if owner.split('::')[0] == source.path} + imports = {(_module(owner.split('::')[0]), owner.split('::')[1]): value + for owner, value in owners.items()} + for node in tree.body: + if isinstance(node, ast.ImportFrom): + for alias in node.names: + name = alias.asname or alias.name + value = imports.get((_import_module(source.path, node), alias.name)) + if value is not None: + bindings[name] = value + else: + bindings.pop(name, None) + elif isinstance(node, ast.Import): + for alias in node.names: + bindings.pop(alias.asname or alias.name.split('.')[0], None) + for node in tree.body: + if isinstance(node, (ast.Assign, ast.AnnAssign)): + targets = node.targets if isinstance(node, ast.Assign) else [node.target] + for target in targets: + if isinstance(target, ast.Name): + bindings.pop(target.id, None) + return bindings + + +def scan_python_production( + source: SourceFile, + *, + field: str | None, + enums: Mapping[str, Mapping[str, str]], + return_functions: frozenset[str] = frozenset(), + return_paths: Mapping[str, tuple[str | int, ...]] | None = None, + call_arguments: Mapping[str, Mapping[str, int | None]] | None = None, +) -> list[Production]: + """Observe writes and owner-member results with bounded local resolution. + + ``enums`` maps module::Class to literal member values from tracked owners. + Only imported owner classes (including aliases) or the local owner qualify. + Local aliases and complete branch selections resolve only at output sites. + General reassignment and parameter shadowing become unknown. Explicit call + metadata names only reviewed builder arguments; arbitrary calls are consumers. + Nested function returns belong to that function, not a registered enclosure. + """ + tree = ast.parse(source.text, filename=source.path) + bindings = _qualified_bindings(source, tree, enums) + call_arguments = call_arguments or {} + calls = _qualified_bindings(source, tree, call_arguments) + return_paths = return_paths or {} + + result: list[Production] = [] + + def matches(node: ast.AST) -> bool: + if isinstance(node, ast.Name): + return node.id == field + if isinstance(node, ast.Attribute): + return node.attr == field + return (isinstance(node, ast.Subscript) and isinstance(node.slice, ast.Constant) + and node.slice.value == field) + + def scan_scope(body: list[ast.stmt], scope: str, parameters: set[str]) -> None: + nodes: list[ast.AST] = [] + nested: list[ast.FunctionDef | ast.AsyncFunctionDef | ast.ClassDef] = [] + + def collect(node: ast.AST) -> None: + if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef, ast.ClassDef)): + nested.append(node) + return + if isinstance(node, ast.Lambda): + return + nodes.append(node) + for child in ast.iter_child_nodes(node): + collect(child) + for statement in body: + collect(statement) + assigned = Counter(n.id for n in nodes if isinstance(n, ast.Name) and isinstance(n.ctx, ast.Store)) + local_owner_names = {owner.split('::')[1] for owner in enums if owner.split('::')[0] == source.path} + nested_names = {n.name for n in nested} + if scope == '': + nested_names -= local_owner_names | {owner.split('::')[1] for owner in call_arguments + if owner.split('::')[0] == source.path} + imported = set() + if scope != '': + for node in nodes: + if isinstance(node, (ast.Import, ast.ImportFrom)): + imported.update(alias.asname or alias.name.split('.')[0] for alias in node.names) + exception_targets = {n.name for n in nodes if isinstance(n, ast.ExceptHandler) and n.name} + deleted = {n.id for n in nodes if isinstance(n, ast.Name) and isinstance(n.ctx, ast.Del)} + shadows = set(assigned) | parameters | nested_names | imported | exception_targets | deleted + local_bindings = {k: v for k, v in bindings.items() if k not in shadows} + local_calls = {k: v for k, v in calls.items() if k not in shadows} + single_values = {} + for node in nodes: + if isinstance(node, ast.Assign) and len(node.targets) == 1 and isinstance(node.targets[0], ast.Name): + target = node.targets[0].id + if assigned[target] == 1 and target not in parameters: + single_values[target] = node.value + elif isinstance(node, ast.AnnAssign) and isinstance(node.target, ast.Name): + target = node.target.id + if assigned[target] == 1 and target not in parameters and node.value is not None: + single_values[target] = node.value + + def conditional_values(node: ast.If) -> dict[str, tuple[ast.AST, int]]: + # A complete if/elif/else defining a local in each arm is one finite + # selection. Partial branches, loops and general reassignments stay + # unknown; no assignment is itself an enum production site. + def arm(statements: list[ast.stmt]) -> dict[str, tuple[ast.AST, int]]: + if len(statements) == 1 and isinstance(statements[0], ast.If): + return conditional_values(statements[0]) + definitions = {} + for statement in statements: + if (isinstance(statement, ast.Assign) and len(statement.targets) == 1 + and isinstance(statement.targets[0], ast.Name)): + name = statement.targets[0].id + definitions[name] = (statement.value, definitions.get(name, (None, 0))[1] + 1) + return {name: item for name, item in definitions.items() if item[1] == 1} + left, right = arm(node.body), arm(node.orelse) + return {name: (ast.copy_location(ast.IfExp(test=node.test, body=left[name][0], + orelse=right[name][0]), node), left[name][1] + right[name][1]) + for name in left.keys() & right.keys()} + + for node in nodes: + if isinstance(node, ast.If): + for name, (value, count) in conditional_values(node).items(): + if assigned[name] == count and name not in parameters: + single_values[name] = value + + # Resolve only local containers that have not been mutated or escaped. + # A subscript write through an alias invalidates every alias, rather + # than turning a stale initializer into false scalar output evidence. + containers = {name for name, value in single_values.items() + if isinstance(value, (ast.List, ast.Dict, ast.Set))} + aliases = [(name, value.id) for name, value in single_values.items() if isinstance(value, ast.Name)] + unsafe: set[str] = set() + + def root_name(node: ast.AST) -> str | None: + while isinstance(node, (ast.Attribute, ast.Subscript)): + node = node.value + return node.id if isinstance(node, ast.Name) else None + + for node in nodes: + if isinstance(node, (ast.Attribute, ast.Subscript)) and isinstance(node.ctx, (ast.Store, ast.Del)): + if name := root_name(node): + unsafe.add(name) + elif isinstance(node, ast.Call): + if isinstance(node.func, ast.Attribute) and (name := root_name(node.func)): + unsafe.add(name) + for argument in [*node.args, *(kw.value for kw in node.keywords)]: + if isinstance(argument, ast.Name): + unsafe.add(argument.id) + for group in (containers, unsafe): + changed = True + while changed: + before = len(group) + for left, right in aliases: + if left in group or right in group: + group.update((left, right)) + changed = len(group) != before + for name in containers & unsafe: + single_values.pop(name, None) + + def bound(node: ast.AST | None, seen: frozenset[str]) -> tuple[ast.AST | None, frozenset[str]]: + while isinstance(node, ast.Name) and node.id in single_values and node.id not in seen: + definition = single_values[node.id] + if (definition.lineno, definition.col_offset) >= (node.lineno, node.col_offset): + break + seen = seen | {node.id} + node = definition + return node, seen + + def index_value(node: ast.AST) -> str | int | None: + if isinstance(node, ast.Constant) and type(node.value) in (str, int): + return node.value + if (isinstance(node, ast.UnaryOp) and isinstance(node.op, ast.USub) + and isinstance(node.operand, ast.Constant) and type(node.operand.value) is int): + return -node.operand.value + return None + + def lookup(container: ast.AST | None, key: str | int | None) -> tuple[list[ast.AST], bool]: + if isinstance(container, (ast.Tuple, ast.List)): + if type(key) is int: + return ([container.elts[key]], False) if -len(container.elts) <= key < len(container.elts) else ([], True) + if key is not None: + return [], True + return list(container.elts), True + if isinstance(container, ast.Dict): + keys = [index_value(k) if k is not None else None for k in container.keys] + if key is not None and all(k is not None for k in keys): + # Python dict construction keeps the last duplicate key. + found = [v for k, v in zip(keys, container.values, strict=True) if k == key] + return ([found[-1]], False) if found else ([], True) + return list(container.values), True + return [], True + + def resolve(node: ast.AST | None, seen: frozenset[str] = frozenset(), *, enum_only: bool = False) -> tuple[set[str], bool]: + node, seen = bound(node, seen) + if isinstance(node, ast.Constant): + if isinstance(node.value, str): + return ({node.value} if node.value and not enum_only else set()), False + return set(), node.value is not None + if isinstance(node, ast.Subscript): + if isinstance(node.slice, ast.Slice) or (isinstance(node.slice, ast.Constant) + and type(node.slice.value) not in (str, int)): + return set(), True + container, visited = bound(node.value, seen) + choices, unknown = lookup(container, index_value(node.slice)) + known: set[str] = set() + for value in choices: + part, unresolved = resolve(value, visited, enum_only=enum_only) + known.update(part) + unknown |= unresolved + return known, unknown + if isinstance(node, (ast.IfExp, ast.BoolOp)): + operands = [node.body, node.orelse] if isinstance(node, ast.IfExp) else node.values + parts = [resolve(value, seen, enum_only=enum_only) for value in operands] + return set().union(*(values for values, _ in parts)), any(unknown for _, unknown in parts) + if (isinstance(node, ast.Call) and isinstance(node.func, ast.Name) + and node.func.id == 'str' and node.func.id not in shadows + and len(node.args) == 1 and not node.keywords): + return resolve(node.args[0], seen, enum_only=enum_only) + if isinstance(node, ast.Attribute): + member = node.value if node.attr == 'value' else node + if isinstance(member, ast.Attribute) and isinstance(member.value, ast.Name): + members = local_bindings.get(member.value.id) + if members is not None: + if member.attr not in members: + raise ValueError(f'{source.path}:{node.lineno}: unknown owner member {member.attr}') + return {members[member.attr]}, False + if node.attr == 'value' and isinstance(node.value, ast.Name): + return enum_object_value(node.value, seen) + return set(), True + + def enum_object_value(node: ast.AST, seen: frozenset[str]) -> tuple[set[str], bool]: + node, seen = bound(node, seen) + if isinstance(node, ast.IfExp): + parts = [enum_object_value(value, seen) for value in (node.body, node.orelse)] + return set().union(*(v for v, _ in parts)), any(u for _, u in parts) + if isinstance(node, ast.Attribute) and isinstance(node.value, ast.Name) and node.value.id in local_bindings: + return resolve(node, seen, enum_only=True) + # A serialized string (including Action.RUN.value) is not an enum + # object with another .value attribute. + return set(), True + + def returned(node: ast.AST | None, path: tuple[str | int, ...], seen: frozenset[str] = frozenset()) -> tuple[set[str], bool]: + if not path: + return resolve(node, seen) + node, seen = bound(node, seen) + choices, unknown = lookup(node, path[0]) + parts = [returned(value, path[1:], seen) for value in choices] + return set().union(*(v for v, _ in parts)), unknown or any(u for _, u in parts) + + def record(node: ast.AST | None, form: str, location: ast.AST) -> None: + values, unknown = (returned(node, return_paths.get(scope, ())) if form == 'return' else resolve(node)) + if form == 'keyword_unproved': + unknown = True # Argument name alone does not prove an output role. + result.append(Production(f'{source.path}::{scope}', location.lineno, form, frozenset(values), unknown)) + + for node in nodes: + if isinstance(node, ast.Assign): + if field and any(matches(t) for t in node.targets): + record(node.value, 'assignment', node) + elif isinstance(node, ast.AnnAssign) and field and matches(node.target): + record(node.value, 'assignment', node) + elif isinstance(node, ast.Dict) and field: + for key, value in zip(node.keys, node.values, strict=True): + if isinstance(key, ast.Constant) and key.value == field: + record(value, 'dict', node) + elif isinstance(node, ast.Call): + output_arguments = local_calls.get(node.func.id, {}) if isinstance(node.func, ast.Name) else {} + for kw in node.keywords: + if kw.arg in output_arguments: + record(kw.value, 'call_argument', node) + elif field and kw.arg == field: + record(kw.value, 'keyword_unproved', node) + for position in output_arguments.values(): + if position is not None and position < len(node.args): + record(node.args[position], 'call_argument', node) + elif isinstance(node, ast.Return) and node.value is not None: + if scope in return_functions: + record(node.value, 'return', node) + else: + values, unknown = resolve(node.value, enum_only=True) + if values: + result.append(Production(f'{source.path}::{scope}', node.lineno, 'enum_result', frozenset(values), unknown)) + for child in nested: + name = child.name if scope == '' else f'{scope}.{child.name}' + params: set[str] = set() + if not isinstance(child, ast.ClassDef): + args = child.args + params = {a.arg for a in (*args.posonlyargs, *args.args, *args.kwonlyargs)} + params.update(a.arg for a in (args.vararg, args.kwarg) if a) + # A nested closure might shadow an owner in any enclosing scope. + scan_scope(child.body, name, params | shadows) + + scan_scope(tree.body, '', set()) + return sorted(set(result), key=lambda row: (row.site, row.line, row.form, sorted(row.values))) diff --git a/loopx/semantics/vocabulary_v0.json b/loopx/semantics/vocabulary_v0.json index 7d0adb2956..ccf406ede1 100644 --- a/loopx/semantics/vocabulary_v0.json +++ b/loopx/semantics/vocabulary_v0.json @@ -1,9 +1,8 @@ { "schema_version": "loopx_semantic_vocabulary_v0", "rfc": "docs/architecture/rfcs/semantic-vocabulary-convergence-v0.md", - "inventory": "loopx/semantics/inventory_v0.json", "policy": { - "scope": "Repository-wide. The curated vocabularies below are the kernel and cross-runtime sets; the generated inventory maps every other closed-set carrier under loopx/ so additions are visible in a diff.", + "scope": "Repository-wide. The curated vocabularies below are the kernel and cross-runtime sets; the generated inventory maps every other closed-set carrier under loopx/ so additions are visible in the full-tree scan and optional report.", "closed_sets": "Every value a registered vocabulary may carry is listed here. Adding, renaming, or removing a value is a registry edit reviewed with the code change; an unregistered literal fails the drift smoke.", "owner_exclusivity": "A registered symbol is defined only in its listed owner modules. Any other module imports it instead of redefining the literal. An owner is always module::Symbol or null; a null owner requires a literal_scan.", "budgets_only_decrease": "Retirement, twin, and inventory-ratchet budgets only fall. Lowering a budget is a routine edit; raising one needs the maintainer approval recorded in the RFC decision log. Each budget must equal its BUDGET_ANCHOR or RETIREMENT_ANCHOR literal in the drift smoke, so tightening edits the JSON and the code literal in one diff, and a later diff cannot raise the JSON back toward a stale anchor.", @@ -114,7 +113,7 @@ "owner_carrier_set_equality", "cross_runtime_owner_parity", "declared_projection_mapping", - "inventory_snapshot_freshness" + "current_tree_inventory" ], "bounded": [ "fixed_literal_forms", @@ -135,7 +134,7 @@ }, "coverage_floor": { "vocabularies": 26, - "owner_symbols": 46, + "owner_symbols": 49, "literal_scan_fields": 1, "projections": 1, "relations": 9, @@ -173,6 +172,30 @@ "validation_failed": "Legacy failure class per turn-loop-controller-v0; always routes to repair.", "writeback_failed": "Legacy failure class per turn-loop-controller-v0; always routes to repair.", "quota_spend_failed": "Legacy failure class per turn-loop-controller-v0; always routes to repair." + }, + "input_producer": "loopx/control_plane/turn_driver/transaction.py::_result_kind", + "producers": [ + "loopx/control_plane/turn_driver/executor.py::_host_result_stage", + "loopx/control_plane/turn_driver/executor.py::_run_task_validator", + "loopx/control_plane/turn_driver/executor.py::_task_validation_receipt", + "loopx/control_plane/turn_driver/executor.py::_task_validation_stage", + "loopx/control_plane/turn_driver/transaction.py::_result_kind" + ], + "call_producers": { + "loopx/control_plane/turn_driver/executor.py::_host_failure": [ + "kind" + ], + "loopx/control_plane/turn_driver/executor.py::_task_validation_receipt": [ + "recovery_kind" + ] + }, + "return_producers": [ + "loopx/control_plane/turn_driver/executor.py::_task_validation_receipt" + ], + "return_paths": { + "loopx/control_plane/turn_driver/executor.py::_task_validation_receipt": [ + "recovery_kind" + ] } }, "turn_route": { @@ -192,7 +215,23 @@ "wait", "blocked", "contract_error" - ] + ], + "producers": [ + "loopx/control_plane/turn_driver/driver.py::_typed_route", + "loopx/control_plane/turn_driver/driver.py::build_loopx_turn_plan", + "loopx/control_plane/turn_driver/loop_controller.py::_envelope_route" + ], + "return_producers": [ + "loopx/control_plane/turn_driver/driver.py::_typed_route", + "loopx/control_plane/turn_driver/loop_controller.py::_envelope_route", + "loopx/control_plane/turn_driver/driver.py::build_loopx_turn_plan" + ], + "return_paths": { + "loopx/control_plane/turn_driver/driver.py::build_loopx_turn_plan": [ + "route", + "kind" + ] + } }, "loop_disposition": { "meaning": "Pure controller verdict for the outer loop after combining the last Turn receipt with the fresh route.", @@ -211,27 +250,56 @@ "repair", "replan", "terminal" - ] + ], + "producers": [ + "loopx/control_plane/turn_driver/loop_controller.py::_completion_disposition", + "loopx/control_plane/turn_driver/loop_controller.py::_replan_disposition", + "loopx/control_plane/turn_driver/loop_controller.py::_route_to_disposition", + "loopx/control_plane/turn_driver/loop_controller.py::decide_loop_disposition" + ], + "return_producers": [ + "loopx/control_plane/turn_driver/loop_controller.py::_route_to_disposition" + ], + "call_producers": { + "loopx/control_plane/turn_driver/loop_controller.py::_disposition": [ + "disposition" + ] + } }, "agent_scope_frontier_action": { - "meaning": "Frontier verdict for an agent-scoped lane; its value is written into the agent_scope_frontier.effective_action slot of the Turn Envelope.", + "meaning": "Frontier verdict for an agent-scoped lane. New v1 payloads use agent_scope_frontier.action; the root should-run effective_action projects the same value through its registered disjoint union. Legacy v0 signed payloads remain readable without rewriting.", "tier": "kernel", "status": "canonical", "owners": { "python": "loopx/control_plane/agents/agent_scope_frontier.py::AgentScopeFrontierAction", - "typescript": null + "typescript": "loopx/control_plane/agents/agent_scope_frontier.generated.ts::AGENT_SCOPE_FRONTIER_ACTIONS" }, "values": [ "agent_scope_exhausted", "agent_scope_wait", "reassignment_required", "successor_replan_required" - ] + ], + "producers": [ + "loopx/control_plane/agents/agent_scope.py::_blocked_successor_wait_frontier", + "loopx/control_plane/agents/agent_scope.py::_blocking_handoff_frontier", + "loopx/control_plane/agents/agent_scope.py::_cleared_handoff_frontier", + "loopx/control_plane/agents/agent_scope.py::_deferred_resume_frontier", + "loopx/control_plane/agents/agent_scope.py::_monitor_blocked_resume_frontier", + "loopx/control_plane/agents/agent_scope.py::_other_agent_or_exhausted_frontier", + "loopx/control_plane/agents/agent_scope.py::_route_continuation_frontier", + "loopx/control_plane/agents/agent_scope.py::_selected_candidate_priority_frontier" + ], + "call_producers": { + "loopx/control_plane/agents/agent_scope_frontier.py::build_agent_scope_frontier_payload": [ + "action" + ] + } }, "lease_action": { "meaning": "Authority-core lease mutation verb.", "tier": "kernel", - "status": "canonical", + "status": "legacy", "owners": { "python": "loopx/control_plane/coordination/authority_core.py::LeaseAction", "typescript": null @@ -241,15 +309,40 @@ "renew", "transfer", "release" - ] + ], + "producers": [], + "compatibility_only": { + "acquire": { + "reason": "Retained by the legacy typed LeaseModeGateCommand input interface; current in-repository runtime callers use the separate acquire/renew/transfer/release command classes, not this vocabulary. No persisted use is asserted.", + "retirement": "M4: retire the legacy Python lease-mode input interface after caller and migration review." + }, + "renew": { + "reason": "Retained by the legacy typed LeaseModeGateCommand input interface; current in-repository runtime callers use the separate acquire/renew/transfer/release command classes, not this vocabulary. No persisted use is asserted.", + "retirement": "M4: retire the legacy Python lease-mode input interface after caller and migration review." + }, + "transfer": { + "reason": "Retained by the legacy typed LeaseModeGateCommand input interface; current in-repository runtime callers use the separate acquire/renew/transfer/release command classes, not this vocabulary. No persisted use is asserted.", + "retirement": "M4: retire the legacy Python lease-mode input interface after caller and migration review." + }, + "release": { + "reason": "Retained by the legacy typed LeaseModeGateCommand input interface; current in-repository runtime callers use the separate acquire/renew/transfer/release command classes, not this vocabulary. No persisted use is asserted.", + "retirement": "M4: retire the legacy Python lease-mode input interface after caller and migration review." + } + }, + "value_notes": { + "acquire": "Compatibility-only input member; no observed in-repository producer. Preserve the typed caller interface until its M4 retirement review.", + "renew": "Compatibility-only input member; no observed in-repository producer. Preserve the typed caller interface until its M4 retirement review.", + "transfer": "Compatibility-only input member; no observed in-repository producer. Preserve the typed caller interface until its M4 retirement review.", + "release": "Compatibility-only input member; no observed in-repository producer. Preserve the typed caller interface until its M4 retirement review." + } }, "effective_action": { - "meaning": "Compacted should-run verdict carried by status/should-run payloads and the Turn Envelope; consumers dispatch on it by string comparison. No enum exists on either runtime, so the owner is null and the literal scan is the only check.", + "meaning": "Compacted should-run verdict carried by status/should-run payloads and the Turn Envelope; the Python enum owns the finite value domain while the wire field remains a string for compatibility.", "tier": "kernel", "status": "merge_candidate", "owners": { - "python": null, - "typescript": null + "python": "loopx/control_plane/quota/effective_action.py::EffectiveAction", + "typescript": "loopx/control_plane/quota/effective_action.generated.ts::EFFECTIVE_ACTIONS" }, "literal_scan": { "field": "effective_action", @@ -266,11 +359,13 @@ "agent_workspace_repair", "automation_prompt_upgrade_required", "autonomous_replan_required", - "block_replay", + "blocked_health", + "blocked_wait", "boundary_projection_repair", "capability_bridge_repair", "control_plane_health_repair", "control_plane_projection_repair", + "control_plane_repair", "coordinate_task_bundle", "external_evidence_observe", "governed_capability_intent", @@ -280,32 +375,55 @@ "monitor_due", "monitor_quiet_skip", "normal_run", - "observe_replay", - "operator_gate", + "operator_gate_notify", "operator_inbox_material_review_due", "outcome_floor_recovery", "peer_coordination_blocked", - "quota_action_selection_deferred", - "quota_action_selection_rejected", "quota_skip", "runtime_user_gate_projection_repair", "scoped_user_gate_fallback", - "skip", "state_projection_gap_repair", "terminal_no_followup", + "throttled_skip", "todo_decision_scope_projection_repair", "unsettled_host_turn_recovery" ], - "variable_sourced_values": { - "quota_action_selection_deferred": "loopx/cli_commands/quota.py", - "quota_action_selection_rejected": "loopx/cli_commands/quota.py" - }, + "producers": [ + "loopx/cli_commands/quota.py::_apply_requested_quota_action_selection_preflight", + "loopx/control_plane/quota/decision_summary.py::_task_orchestration_effective_action", + "loopx/control_plane/quota/decision_summary.py::quota_effective_action", + "loopx/control_plane/quota/decision_summary.py::resolve_quota_run_decision", + "loopx/control_plane/quota/heartbeat_receipt.py::fail_heartbeat_receipt", + "loopx/control_plane/quota/live_decision.py::_apply_pending_capability_intent_precedence", + "loopx/control_plane/quota/projection_repair.py::build_boundary_projection_repair_hint", + "loopx/control_plane/quota/projection_repair.py::build_state_projection_gap_repair_hint", + "loopx/control_plane/quota/settlement_precedence.py::apply_settled_replay_payload_precedence", + "loopx/control_plane/quota/settlement_precedence.py::apply_settled_replay_route_precedence", + "loopx/control_plane/quota/should_run.py::build_quota_paused_should_run_payload", + "loopx/control_plane/quota/should_run_packet.py::_apply_agent_monitor_only_precedence", + "loopx/control_plane/quota/should_run_packet.py::_resolve_quota_should_run_route", + "loopx/control_plane/quota/stall_repair.py::build_quota_stall_self_repair_hint", + "loopx/control_plane/quota/stall_repair.py::build_runtime_capability_user_gate_repair_hint", + "loopx/control_plane/quota/unsettled_host_turn.py::apply_unsettled_host_turn_recovery_if_required", + "loopx/control_plane/todos/decision_scope.py::build_required_decision_scope_repair_hint", + "loopx/control_plane/todos/user_gate.py::apply_scoped_user_gate_fallback_projection" + ], + "compatibility_only": {}, "value_notes": { - "skip": "Compared in loopx/control_plane/todos/user_gate.py; no producer writes it into effective_action. Candidate dead value (decision-level skip lives in the decision field).", - "observe_replay": "Written only by turn_journal.ts into the replay observation slot; not a should-run verdict. Slot split is RFC Section 12 Q6.", - "block_replay": "Written only by turn_journal.ts into the replay observation slot; not a should-run verdict. Slot split is RFC Section 12 Q6.", - "quota_action_selection_deferred": "Reaches the slot through the error_code variable in cli_commands/quota.py; an error code doubling as a verdict.", - "quota_action_selection_rejected": "Reaches the slot through the error_code variable in cli_commands/quota.py; an error code doubling as a verdict." + "blocked_health": "Existing result of quota_effective_action; previously missed because the literal scan did not inspect declared return functions.", + "blocked_wait": "Existing result of quota_effective_action; previously missed because the literal scan did not inspect declared return functions.", + "control_plane_repair": "Existing result of quota_effective_action; previously missed because the literal scan did not inspect declared return functions.", + "operator_gate_notify": "Existing result of quota_effective_action; previously missed because the literal scan did not inspect declared return functions.", + "throttled_skip": "Existing result of quota_effective_action; previously missed because the literal scan did not inspect declared return functions." + }, + "return_producers": [ + "loopx/control_plane/quota/decision_summary.py::quota_effective_action", + "loopx/control_plane/quota/decision_summary.py::_task_orchestration_effective_action" + ], + "return_paths": { + "loopx/control_plane/quota/decision_summary.py::_task_orchestration_effective_action": [ + 0 + ] } }, "settlement_step_kind": { @@ -667,22 +785,14 @@ "field": "effective_action", "slots": [ { - "slot": "decision.effective_action", - "vocabulary": "effective_action" - }, - { - "slot": "agent_scope_frontier.effective_action", - "vocabulary": "agent_scope_frontier_action" - }, - { - "slot": "turn_journal_replay.observation.effective_action", - "values": [ - "observe_replay", - "block_replay" + "slot": "should_run.effective_action", + "vocabularies": [ + "effective_action", + "agent_scope_frontier_action" ] } ], - "note": "One field name, three vocabularies, inside one Turn Envelope. The literal scan cannot tell slots apart, so the decision vocabulary lists the replay values with notes until Q6 splits them." + "note": "The root should-run/Turn Envelope action is an explicitly registered disjoint union of decision and frontier actions. Nested frontier v1 uses action only; legacy v0 fields remain readable without rewriting signed snapshots. Replay uses observation.decision, and selection failures use quota_skip plus error_code." } ], "subsets": [ @@ -743,27 +853,27 @@ "meaning": "Decision fields the should-run documentation already calls legacy. Budgets count modules under loopx/ that still mention the field.", "fields": { "execution_obligation": { - "python_module_budget": 21, + "python_module_budget": 20, "typescript_module_budget": 1 }, "heartbeat_recommendation": { - "python_module_budget": 18, + "python_module_budget": 17, "typescript_module_budget": 1 }, "work_lane_contract": { - "python_module_budget": 32, + "python_module_budget": 29, "typescript_module_budget": 3 }, "external_evidence_observation": { - "python_module_budget": 11, + "python_module_budget": 8, "typescript_module_budget": 1 }, "goal_boundary": { - "python_module_budget": 35, + "python_module_budget": 30, "typescript_module_budget": 2 }, "protocol_action_packet": { - "python_module_budget": 7, + "python_module_budget": 5, "typescript_module_budget": 2 } } @@ -786,8 +896,32 @@ "multi_value_fork_definitions": 10, "same_runtime_forks_semantic": 18, "conflicting_values_semantic": 2, - "multi_value_meaning": "Enums, named closed sets, Literal aliases, and TypeScript as-const arrays are vocabulary exactly as a NAME = \"value\" constant is, so they get the same collision rule. One name defined in two modules with identical values is a twin; with different values it is a fork.", + "multi_value_meaning": "Enums, named closed sets, Literal aliases, and TypeScript as-const arrays are vocabulary exactly as a NAME = \"value\" constant is, so they get the same collision rule. One name defined in two modules with identical values is a twin; with different values it is a fork. The semantic multi-value-fork budget excludes only names declared in scope_declarations.", "multi_value_forks_note": "The 4 counted forks include SOURCE_SURFACES, whose four definitions are four CLI commands each listing its own data sources; that is bounded-context reuse of one name, not drift. It stays in the budget until M0.5 adds a scope field (RFC Section 5) and must not be removed by renaming.", - "semantic_meaning": "same_runtime_forks_semantic and conflicting_values_semantic exclude module-local convention names such as SCHEMA_VERSION, COMMAND, or *_LABEL, which every module legitimately names for itself. The remaining names are shared vocabulary, where a duplicate is real drift rather than local naming; the unfiltered totals stay visible in the generated inventory summary." + "semantic_meaning": "same_runtime_forks_semantic and conflicting_values_semantic exclude module-local convention names such as SCHEMA_VERSION, COMMAND, or *_LABEL, which every module legitimately names for itself. The remaining names are shared vocabulary, where a duplicate is real drift rather than local naming; the unfiltered totals stay visible in the generated inventory summary.", + "multi_value_forks_semantic": 3 + }, + "scope_declarations": { + "SOURCE_SURFACES": { + "kind": "bounded_context", + "contexts": [ + { + "id": "global_risks", + "owner": "loopx/global_risks.py::SOURCE_SURFACES" + }, + { + "id": "global_todos", + "owner": "loopx/global_todos.py::SOURCE_SURFACES" + }, + { + "id": "summary_all", + "owner": "loopx/summary_all.py::SOURCE_SURFACES" + }, + { + "id": "pr_review", + "owner": "loopx/pr_review.py::SOURCE_SURFACES" + } + ] + } } } diff --git a/scripts/generate_semantic_bindings.py b/scripts/generate_semantic_bindings.py new file mode 100644 index 0000000000..c456a71985 --- /dev/null +++ b/scripts/generate_semantic_bindings.py @@ -0,0 +1,126 @@ +#!/usr/bin/env python3 +"""Generate the quota/frontier TypeScript bindings and vocabulary glossary. + +Run with ``uv run python scripts/generate_semantic_bindings.py [--check]``. +The Python enum remains the value owner during M1. Product code does not import +this generator or the build-time semantic registry. +""" +from __future__ import annotations + +import argparse +import json +from pathlib import Path +import sys + +ROOT = Path(__file__).resolve().parents[1] +if str(ROOT) not in sys.path: + sys.path.insert(0, str(ROOT)) + +from loopx.semantics.inventory import load_sources # noqa: E402 +from loopx.semantics.python_production import enum_members # noqa: E402 +from loopx.semantics.production import quota_action_domain # noqa: E402 + +REGISTRY = ROOT / 'loopx/semantics/vocabulary_v0.json' +BINDING = ROOT / 'loopx/control_plane/quota/effective_action.generated.ts' +FRONTIER_BINDING = ROOT / 'loopx/control_plane/agents/agent_scope_frontier.generated.ts' +GLOSSARY = ROOT / 'docs/reference/glossary.md' + + +def render_binding(members: dict[str, str], source_owner: str, symbol: str, array: str) -> str: + lines = [ + '// Generated by scripts/generate_semantic_bindings.py; do not edit.', + f'// Value owner: {source_owner}', + '', + f'export const {array} = [', + *(f' {json.dumps(value)},' for value in members.values()), + '] as const;', + f'export type {symbol}Value = (typeof {array})[number];', + '', + f'export const {symbol} = {{', + *(f' {name}: {array}[{index}],' for index, name in enumerate(members)), + '} as const;', + '', + ] + return '\n'.join(lines) + + +def render_glossary(registry: dict) -> str: + lines = [ + '# Semantic Vocabulary Glossary / 语义词表', + '', + '', + '', + 'This is a generated view of the curated registry, not another authority.', + '这是注册表的生成视图;修改定义应编辑 owner 和注册表,然后重新生成。', + '', + 'Equal spellings or value sets do not prove equal meaning. Producers write', + 'values; consumers interpret or pass them through; only owners define sets.', + '同名或相同值集不等于同一语义。生产者写值,消费者解释或透传,owner 定义集合。', + '', + 'See the [RFC](../architecture/rfcs/semantic-vocabulary-convergence-v0.md)', + 'and its [中文版本](../architecture/rfcs/semantic-vocabulary-convergence-v0.zh-CN.md)', + 'for scope, compatibility, proof boundaries and migration gates.', + '', + ] + for name, vocabulary in sorted(registry['vocabularies'].items()): + lines += [f'## {name}', '', vocabulary['meaning'], '', + f"- Tier / 层级: `{vocabulary['tier']}`; status / 状态: `{vocabulary['status']}`."] + for runtime, owner in vocabulary['owners'].items(): + if owner: + module, symbol = owner.split('::') + lines.append(f'- {runtime}: [`{symbol}`](../../{module}).') + lines.append('- Values / 值: ' + ', '.join(f'`{value}`' for value in vocabulary['values']) + '.') + if compatibility := vocabulary.get('compatibility_only'): + lines.append('- Compatibility only / 兼容保留: ' + ', '.join(f'`{value}`' for value in sorted(compatibility)) + '.') + lines.append('') + return '\n'.join(lines) + + +def build_artifacts() -> dict[Path, str]: + registry = json.loads(REGISTRY.read_text(encoding='utf-8')) + sources = {source.path: source for source in load_sources(ROOT)} + artifacts = {} + for name, path, array in ( + ('effective_action', BINDING, 'EFFECTIVE_ACTIONS'), + ('agent_scope_frontier_action', FRONTIER_BINDING, 'AGENT_SCOPE_FRONTIER_ACTIONS'), + ): + vocabulary = registry['vocabularies'][name] + owner = vocabulary['owners']['python'] + module, symbol = owner.split('::') + if module not in sources: + raise ValueError(f'{name} owner must be a tracked source') + members = enum_members(sources[module], symbol, strict=True) + values = list(members.values()) + if (len(values) != len(set(values)) or set(values) != set(vocabulary['values'])): + raise ValueError(f'{name} owner and registry differ; reconcile them before generation') + artifacts[path] = render_binding(members, owner, symbol, array) + quota_action_domain(registry) + artifacts[BINDING] += ( + '\nimport type { AgentScopeFrontierActionValue } from "../agents/agent_scope_frontier.generated.ts";\n' + 'export type QuotaEffectiveActionValue = EffectiveActionValue | AgentScopeFrontierActionValue;\n' + ) + artifacts[GLOSSARY] = render_glossary(registry) + return artifacts + + +def main() -> int: + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument('--check', action='store_true', help='fail without writing if either artifact is stale') + args = parser.parse_args() + stale = [] + for path, expected in build_artifacts().items(): + if path.is_file() and path.read_text(encoding='utf-8') == expected: + continue + stale.append(path.relative_to(ROOT).as_posix()) + if not args.check: + path.write_text(expected, encoding='utf-8') + if stale and args.check: + print('Stale semantic artifacts: ' + ', '.join(stale) + + '; run uv run python scripts/generate_semantic_bindings.py', file=sys.stderr) + return 1 + print('semantic bindings/glossary: ' + ('generated' if stale else 'up to date')) + return 0 + + +if __name__ == '__main__': + raise SystemExit(main()) diff --git a/scripts/generate_semantic_inventory.py b/scripts/generate_semantic_inventory.py index c7da0ff5d3..60ea818471 100755 --- a/scripts/generate_semantic_inventory.py +++ b/scripts/generate_semantic_inventory.py @@ -1,10 +1,11 @@ #!/usr/bin/env python3 -"""Generate or check the repository-wide semantic inventory. +"""Compute the full tracked-tree inventory; optionally export a local report. Usage: - uv run python scripts/generate_semantic_inventory.py # rewrite inventory_v0.json - uv run python scripts/generate_semantic_inventory.py --check # exit 1 when the file is stale - uv run python scripts/generate_semantic_inventory.py --report # print advisory consumer ranking + uv run python scripts/generate_semantic_inventory.py # JSON to stdout, no writes + uv run python scripts/generate_semantic_inventory.py --output .local/inventory.json + uv run python scripts/generate_semantic_inventory.py --output .local/inventory.json --check + uv run python scripts/generate_semantic_inventory.py --report # advisory consumer ranking """ from __future__ import annotations @@ -25,38 +26,41 @@ render_inventory, ) -INVENTORY_PATH = ROOT / "loopx" / "semantics" / "inventory_v0.json" - - def main() -> int: parser = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter) - parser.add_argument("--check", action="store_true", help="fail when the committed inventory is stale") - parser.add_argument("--report", action="store_true", help="print the advisory consumer ranking") + destination = parser.add_mutually_exclusive_group() + destination.add_argument("--output", type=Path, help="write an optional report to this path instead of stdout") + destination.add_argument("--report", action="store_true", help="print the advisory consumer ranking") + parser.add_argument("--check", action="store_true", help="compare an explicit --output report without writing") parser.add_argument("--top", type=int, default=25, help="rows to print with --report") args = parser.parse_args() + if args.check and args.output is None: + parser.error("--check requires --output; inventories are no longer committed. " + "Run examples/semantic-vocabulary-drift-smoke.py for semantic validation.") inventory = build_inventory(ROOT) content = render_inventory(inventory) if args.report: rows = consumer_ranking(inventory, load_sources(ROOT))[: args.top] - width = max(len(row["name"]) for row in rows) + width = max((len(row["name"]) for row in rows), default=0) print("external_consumer_modules values name module") for row in rows: print(f"{row['external_consumer_modules']:>25} {row['values']:>6} {row['name']:<{width}} {row['module']}") return 0 - current = INVENTORY_PATH.read_text(encoding="utf-8") if INVENTORY_PATH.exists() else None - if current == content: - print(f"semantic inventory up to date: {INVENTORY_PATH.relative_to(ROOT)}") + if args.output is None: + print(content, end="") return 0 if args.check: - print( - f"stale semantic inventory: {INVENTORY_PATH.relative_to(ROOT)}; " - "from the repository root run uv run python scripts/generate_semantic_inventory.py and commit the result", - file=sys.stderr, - ) - return 1 - INVENTORY_PATH.write_text(content, encoding="utf-8") - print(f"generated {INVENTORY_PATH.relative_to(ROOT)}") + current = args.output.read_text(encoding="utf-8") if args.output.exists() else None + if current != content: + print(f"stale or missing semantic inventory report: {args.output}; " + "rerun with the same --output path without --check", file=sys.stderr) + return 1 + print(f"semantic inventory report up to date: {args.output}") + return 0 + args.output.parent.mkdir(parents=True, exist_ok=True) + args.output.write_text(content, encoding="utf-8") + print(f"generated semantic inventory report: {args.output}") print(json.dumps(inventory["summary"], indent=2)) return 0 diff --git a/scripts/semantic_production_scan.mjs b/scripts/semantic_production_scan.mjs new file mode 100644 index 0000000000..d1a6b0efec --- /dev/null +++ b/scripts/semantic_production_scan.mjs @@ -0,0 +1,100 @@ +#!/usr/bin/env node +// Parse supplied tracked source text only; never load or execute product modules. +import ts from 'typescript'; + +let input = ''; +for await (const chunk of process.stdin) input += chunk; +const request = JSON.parse(input); +const result = []; +for (const source of request.sources) { + const tree = ts.createSourceFile(source.path, source.text, ts.ScriptTarget.Latest, true, ts.ScriptKind.TS); + if (tree.parseDiagnostics.length) { + const line = tree.getLineAndCharacterOfPosition(tree.parseDiagnostics[0].start ?? 0).line + 1; + process.stdout.write(JSON.stringify({error: {path: source.path, line, code: "typescript_syntax"}})); + process.exit(2); + } + const field = request.field; + const returns = new Set((request.return_functions ?? []).filter(x => x.startsWith(`${source.path}::`))); + const unwrap = node => { + while (node && (ts.isParenthesizedExpression(node) || ts.isAsExpression(node) || ts.isSatisfiesExpression(node))) node = node.expression; + return node; + }; + const values = expression => { + const node = unwrap(expression); + if (!node) return {values: [], unresolved: true}; + if (ts.isStringLiteral(node) || ts.isNoSubstitutionTemplateLiteral(node)) return {values: node.text ? [node.text] : [], unresolved: false}; + if (node.kind === ts.SyntaxKind.NullKeyword) return {values: [], unresolved: false}; + if (ts.isConditionalExpression(node)) { + const left = values(node.whenTrue), right = values(node.whenFalse); + return {values: [...new Set([...left.values, ...right.values])].sort(), unresolved: left.unresolved || right.unresolved}; + } + return {values: [], unresolved: true}; + }; + const staticName = expression => { + const node = unwrap(expression); + return node && (ts.isStringLiteral(node) || ts.isNoSubstitutionTemplateLiteral(node)) ? node.text : null; + }; + const named = node => { + if (!node) return null; + if (ts.isComputedPropertyName(node)) return staticName(node.expression); + return ts.isIdentifier(node) ? node.text : staticName(node); + }; + const target = node => ts.isIdentifier(node) ? node.text === field + : ts.isPropertyAccessExpression(node) ? node.name.text === field + : ts.isElementAccessExpression(node) && staticName(node.argumentExpression) === field; + const reads = expression => { + const node = unwrap(expression); + if (!node) return false; + if (target(node)) return true; + if (ts.isCallExpression(node) && ts.isIdentifier(node.expression) && + node.expression.text === 'String' && node.arguments.length === 1) return reads(node.arguments[0]); + return ts.isBinaryExpression(node) && + [ts.SyntaxKind.BarBarToken, ts.SyntaxKind.QuestionQuestionToken].includes(node.operatorToken.kind) && + reads(node.left) && (staticName(node.right) === "" || unwrap(node.right).kind === ts.SyntaxKind.NullKeyword); + }; + const literals = expression => { + const node = unwrap(expression); + if (!node) return []; + if (ts.isArrayLiteralExpression(node)) return node.elements.flatMap(literals); + if (ts.isNewExpression(node) && ts.isIdentifier(node.expression) && node.expression.text === 'Set') { + return (node.arguments ?? []).flatMap(literals); + } + return values(node).values; + }; + function walk(node, scope) { + if (ts.isFunctionDeclaration(node) || ts.isMethodDeclaration(node)) scope = scope === '' ? named(node.name) : `${scope}.${named(node.name)}`; + else if (ts.isArrowFunction(node) || ts.isFunctionExpression(node)) { + const parent = node.parent; + const name = ts.isVariableDeclaration(parent) || ts.isPropertyAssignment(parent) ? named(parent.name) : null; + scope = name ? (scope === '' ? name : `${scope}.${name}`) : ''; + } + let expression, form; + if (ts.isPropertyAssignment(node) && named(node.name) === field) { expression = node.initializer; form = 'object'; } + else if (ts.isBinaryExpression(node) && node.operatorToken.kind === ts.SyntaxKind.EqualsToken && target(node.left)) { expression = node.right; form = 'assignment'; } + else if (ts.isVariableDeclaration(node) && named(node.name) === field && node.initializer) { expression = node.initializer; form = 'assignment'; } + else if (ts.isReturnStatement(node) && returns.has(`${source.path}::${scope}`)) { expression = node.expression; form = 'return'; } + if (form) { + result.push({site: `${source.path}::${scope}`, line: tree.getLineAndCharacterOfPosition(node.getStart(tree)).line + 1, form, ...values(expression)}); + } + if (request.mode === 'literal_uses') { + let observed = []; + if (ts.isBinaryExpression(node) && [ts.SyntaxKind.EqualsEqualsToken, + ts.SyntaxKind.EqualsEqualsEqualsToken, ts.SyntaxKind.ExclamationEqualsToken, + ts.SyntaxKind.ExclamationEqualsEqualsToken].includes(node.operatorToken.kind)) { + if (reads(node.left)) observed.push(...literals(node.right)); + if (reads(node.right)) observed.push(...literals(node.left)); + } else if (ts.isCallExpression(node) && ts.isPropertyAccessExpression(node.expression) && + ['includes', 'has'].includes(node.expression.name.text) && node.arguments.some(reads)) { + observed.push(...literals(node.expression.expression)); + } else if (ts.isSwitchStatement(node) && reads(node.expression)) { + for (const clause of node.caseBlock.clauses) if (ts.isCaseClause(clause)) observed.push(...literals(clause.expression)); + } + if (observed.length) result.push({site: `${source.path}::${scope}`, + line: tree.getLineAndCharacterOfPosition(node.getStart(tree)).line + 1, + form: 'dispatch', values: observed, unresolved: false}); + } + ts.forEachChild(node, child => walk(child, scope)); + } + walk(tree, ''); +} +process.stdout.write(JSON.stringify(result)); diff --git a/tests/architecture/test_semantic_bindings.py b/tests/architecture/test_semantic_bindings.py new file mode 100644 index 0000000000..f0d1d9138b --- /dev/null +++ b/tests/architecture/test_semantic_bindings.py @@ -0,0 +1,152 @@ +"""Generated bindings must preserve the owner domain and fail closed on drift.""" + +from __future__ import annotations + +import json +from pathlib import Path +import subprocess +import sys + +import pytest + +from loopx.control_plane.quota.effective_action import EffectiveAction +from loopx.control_plane.agents.agent_scope_frontier import AgentScopeFrontierAction +from loopx.semantics.inventory import SourceFile +from scripts import generate_semantic_bindings as generator + + +def test_checked_in_semantic_artifacts_are_fresh() -> None: + result = subprocess.run( + [sys.executable, str(Path(generator.__file__)), "--check"], + cwd=generator.ROOT, capture_output=True, text=True, timeout=60, + ) + assert result.returncode == 0, result.stdout + result.stderr + + +@pytest.mark.parametrize("path, enum, symbol, array", [ + (generator.BINDING, EffectiveAction, "EffectiveAction", "EFFECTIVE_ACTIONS"), + (generator.FRONTIER_BINDING, AgentScopeFrontierAction, "AgentScopeFrontierAction", "AGENT_SCOPE_FRONTIER_ACTIONS"), +]) +def test_typescript_binding_preserves_python_member_names_and_values(path, enum, symbol, array) -> None: + result = subprocess.run( + ["node", "--no-warnings", "--experimental-strip-types", "--input-type=module", "-e", + f"import {{{symbol}, {array}}} from {json.dumps(path.as_uri())};" + f"console.log(JSON.stringify({{members: {symbol}, values: {array}}}));"], + cwd=generator.ROOT, capture_output=True, text=True, timeout=30, + ) + assert result.returncode == 0, result.stderr + binding = json.loads(result.stdout) + assert binding["members"] == {name: member.value for name, member in enum.__members__.items()} + assert binding["values"] == [member.value for member in enum] + + +@pytest.mark.parametrize("initial", [None, "stale artifact\n"]) +def test_check_does_not_repair_stale_or_missing_artifacts(tmp_path, monkeypatch, capsys, initial): + artifact = tmp_path / "binding.ts" + if initial is not None: + artifact.write_text(initial, encoding="utf-8") + monkeypatch.setattr(generator, "ROOT", tmp_path) + monkeypatch.setattr(generator, "build_artifacts", lambda: {artifact: "current artifact\n"}) + monkeypatch.setattr(sys, "argv", ["generate_semantic_bindings.py", "--check"]) + assert generator.main() == 1 + assert "uv run python scripts/generate_semantic_bindings.py" in capsys.readouterr().err + assert (artifact.read_text(encoding="utf-8") if artifact.exists() else None) == initial + + monkeypatch.setattr(sys, "argv", ["generate_semantic_bindings.py"]) + assert generator.main() == 0 + written_at = artifact.stat().st_mtime_ns + assert artifact.read_text(encoding="utf-8") == "current artifact\n" + assert generator.main() == 0 + assert artifact.stat().st_mtime_ns == written_at + monkeypatch.setattr(sys, "argv", ["generate_semantic_bindings.py", "--check"]) + assert generator.main() == 0 + + +@pytest.mark.parametrize("declarations", [ + ' NORMAL = "normal"\n UNKNOWN = "unregistered"\n', + ' NORMAL = "normal"\n ALIAS = "normal"\n', +]) +def test_generator_rejects_owner_mismatch_and_aliases(tmp_path, monkeypatch, declarations): + registry = tmp_path / "registry.json" + registry.write_text(json.dumps({"vocabularies": {"effective_action": { + "owners": {"python": "loopx/owner.py::Action"}, "values": ["normal"], + }}}), encoding="utf-8") + monkeypatch.setattr(generator, "REGISTRY", registry) + source = SourceFile("loopx/owner.py", ".py", 'from enum import Enum\nclass Action(str, Enum):\n' + declarations) + monkeypatch.setattr(generator, "load_sources", lambda root: [source]) + with pytest.raises(ValueError, match="owner and registry differ"): + generator.build_artifacts() + + +@pytest.mark.parametrize('declaration', [ + ' NEW = "review_" + "only"\n', + ' ALIAS = NORMAL_RUN\n', + ' NORMAL_RUN = "normal_run"\n', +]) +def test_generator_rejects_unsupported_or_duplicate_owner_members(monkeypatch, declaration): + sources = generator.load_sources(generator.ROOT) + owner = 'loopx/control_plane/quota/effective_action.py' + changed = [SourceFile(s.path, s.suffix, s.text + declaration) if s.path == owner else s for s in sources] + monkeypatch.setattr(generator, 'load_sources', lambda root: changed) + monkeypatch.setattr(sys, 'argv', ['generate_semantic_bindings.py', '--check']) + with pytest.raises(ValueError, match=r'effective_action.py:\d+:.*member'): + generator.main() + + +def test_generator_does_not_silently_drop_annotated_members(monkeypatch): + sources = generator.load_sources(generator.ROOT) + owner = 'loopx/control_plane/quota/effective_action.py' + changed = [SourceFile(s.path, s.suffix, s.text + ' NEW: str = "review_only"\n') if s.path == owner else s for s in sources] + monkeypatch.setattr(generator, 'load_sources', lambda root: changed) + with pytest.raises(ValueError, match='owner and registry differ'): + generator.build_artifacts() + + +@pytest.mark.parametrize('declarations,member', [ + (' NEW = factory()\n', 'NEW'), + (' NEW = 1\n', 'NEW'), + (' NEW: str\n', 'NEW'), + (' NEW: str = "computed" + "value"\n', 'NEW'), + (' A = B = "new"\n', 'A,B'), + (' A, B = "new", "value"\n', 'A,B'), + (' NORMAL = "run"\n ALIAS = "run"\n', 'ALIAS'), + (' NORMAL = "run"\n NORMAL: str = "wait"\n', 'NORMAL'), + (' _ignore_ = "NORMAL"\n NORMAL = "run"\n', '_ignore_'), +]) +def test_strict_owner_errors_name_the_path_line_and_member(declarations, member): + source = SourceFile('loopx/owner.py', '.py', 'class Action(str, Enum):\n' + declarations) + with pytest.raises(ValueError) as error: + generator.enum_members(source, 'Action', strict=True) + assert str(error.value).startswith('loopx/owner.py:') + assert f'member {member}' in str(error.value) + + +def test_annotated_literal_binding_includes_every_declared_member(monkeypatch): + sources = generator.load_sources(generator.ROOT) + owner = 'loopx/control_plane/quota/effective_action.py' + changed = [SourceFile(s.path, s.suffix, s.text.replace('NORMAL_RUN =', 'NORMAL_RUN: str =')) + if s.path == owner else s for s in sources] + monkeypatch.setattr(generator, 'load_sources', lambda root: changed) + assert generator.build_artifacts()[generator.BINDING] == generator.BINDING.read_text() + + +def test_failed_owner_validation_never_writes_any_artifact(tmp_path, monkeypatch): + sources = generator.load_sources(generator.ROOT) + owner = 'loopx/control_plane/agents/agent_scope_frontier.py' + changed = [SourceFile(s.path, s.suffix, s.text.replace( + 'AGENT_SCOPE_WAIT = "agent_scope_wait"', 'AGENT_SCOPE_WAIT = invoke_source()')) + if s.path == owner else s for s in sources] + monkeypatch.setattr(generator, 'load_sources', lambda root: changed) + artifact = tmp_path / 'binding.ts' + artifact.write_text('preserve me') + monkeypatch.setattr(generator, 'BINDING', artifact) + monkeypatch.setattr(sys, 'argv', ['generate_semantic_bindings.py']) + with pytest.raises(ValueError, match='member AGENT_SCOPE_WAIT'): + generator.main() + assert artifact.read_text() == 'preserve me' + + +def test_strict_extraction_does_not_execute_inspected_source(): + source = SourceFile('loopx/owner.py', '.py', + 'raise RuntimeError("source must not execute")\nclass Action(str, Enum):\n RUN: str = "run"\n') + assert generator.enum_members(source, 'Action', strict=True) == {'RUN': 'run'} diff --git a/tests/architecture/test_semantic_inventory.py b/tests/architecture/test_semantic_inventory.py index 2855793771..2f2c478775 100644 --- a/tests/architecture/test_semantic_inventory.py +++ b/tests/architecture/test_semantic_inventory.py @@ -4,7 +4,7 @@ pin the classification rules from the RFC rather than from scanner output: which carriers count as closed sets, how duplicate constants split into cross-runtime twins, same-runtime forks, and conflicting values, and that the -committed inventory is regenerated with the code it describes. +inventory is computed from the whole tracked tree without a committed snapshot. """ from __future__ import annotations @@ -190,14 +190,59 @@ def test_render_is_deterministic_valid_json(repo: Path) -> None: assert ' {"name": "Kind", "module": "loopx/a.py", "values": ["one", "two"]}' in rendered -def test_committed_inventory_matches_the_tree() -> None: - result = subprocess.run( - [sys.executable, str(REPO_ROOT / "scripts" / "generate_semantic_inventory.py"), "--check"], - capture_output=True, - text=True, - cwd=REPO_ROOT, - ) - assert result.returncode == 0, result.stderr +def test_inventory_cli_defaults_to_json_without_writing(repo: Path, monkeypatch, capsys) -> None: + from scripts import generate_semantic_inventory as generator + + legacy = repo / "loopx/semantics/inventory_v0.json" + _write(repo, "loopx/semantics/inventory_v0.json", "obsolete report, not JSON") + monkeypatch.setattr(generator, "ROOT", repo) + monkeypatch.setattr(sys, "argv", ["generate_semantic_inventory"]) + assert generator.main() == 0 + emitted = json.loads(capsys.readouterr().out) + assert emitted["schema_version"] == INVENTORY_SCHEMA_VERSION + assert emitted["summary"]["source_files"] == 3 + assert legacy.read_text() == "obsolete report, not JSON" + + +def test_optional_inventory_report_check_never_repairs(repo: Path, monkeypatch, capsys) -> None: + from scripts import generate_semantic_inventory as generator + + output = repo / ".local/reports/inventory.json" + monkeypatch.setattr(generator, "ROOT", repo) + args = ["generate_semantic_inventory", "--output", str(output)] + monkeypatch.setattr(sys, "argv", args + ["--check"]) + assert generator.main() == 1 + assert not output.exists() + monkeypatch.setattr(sys, "argv", args) + assert generator.main() == 0 + saved = output.read_bytes() + monkeypatch.setattr(sys, "argv", args + ["--check"]) + assert generator.main() == 0 + with (repo / "loopx/a.py").open("a") as stream: + stream.write('ADDED = ("new", "carrier")\n') + assert generator.main() == 1 + assert output.read_bytes() == saved + assert "stale or missing" in capsys.readouterr().err + + +def test_snapshot_check_requires_an_explicit_report(monkeypatch, capsys) -> None: + from scripts import generate_semantic_inventory as generator + + monkeypatch.setattr(sys, "argv", ["generate_semantic_inventory", "--check"]) + with pytest.raises(SystemExit) as exc: + generator.main() + assert exc.value.code == 2 + assert "--check requires --output" in capsys.readouterr().err + + +def test_full_tree_scan_detects_a_collision_with_an_unchanged_file(repo: Path) -> None: + _write(repo, "loopx/old.py", 'Q9_SHARED = "existing"\n') + subprocess.run(["git", "-C", str(repo), "add", "loopx/old.py"], check=True) + assert not any(row["name"] == "Q9_SHARED" for row in build_inventory(repo)["duplicate_definitions"]["same_runtime_forks"]) + _write(repo, "loopx/new.py", 'Q9_SHARED = "existing"\n') + subprocess.run(["git", "-C", str(repo), "add", "loopx/new.py"], check=True) + fork = next(row for row in build_inventory(repo)["duplicate_definitions"]["same_runtime_forks"] if row["name"] == "Q9_SHARED") + assert set(fork["modules"]) == {"loopx/old.py", "loopx/new.py"} def test_untracked_sources_do_not_change_inventory(repo: Path) -> None: diff --git a/tests/architecture/test_semantic_production.py b/tests/architecture/test_semantic_production.py new file mode 100644 index 0000000000..f4f7eefadc --- /dev/null +++ b/tests/architecture/test_semantic_production.py @@ -0,0 +1,263 @@ +"""Guard the producer/owner distinction using independent finite counterexamples.""" +from __future__ import annotations + +import pytest + +from loopx.semantics.production import collect_production, validate_production, quota_action_domain +from loopx.semantics.python_production import Production +from loopx.semantics.inventory import SourceFile +from pathlib import Path + +ROOT = Path(__file__).resolve().parents[2] +SITE = 'loopx/control_plane/quota/probe.py::emit' + + +def test_composed_field_does_not_widen_canonical_return_or_establish_owner_liveness(): + v = vocabulary() + field_domain = {'run', 'wait', 'frontier_wait'} + normal = Production(SITE, 1, 'dict', frozenset({'run', 'wait'}), False) + frontier = Production(SITE, 2, 'dict', frozenset({'frontier_wait'}), False) + assert validate_production('action', v, [normal, frontier], field_domain=field_domain) == [] + with pytest.raises(ValueError, match='no observed producer'): + validate_production('action', v, [frontier], field_domain=field_domain) + returned = Production(SITE, 3, 'return', frozenset({'frontier_wait'}), False) + with pytest.raises(ValueError, match='unregistered values'): + validate_production('action', v, [normal, returned], field_domain=field_domain) + foreign = Production(SITE + '_undeclared', 4, 'dict', frozenset({'frontier_wait'}), False) + with pytest.raises(ValueError, match='undeclared producer'): + validate_production('action', v, [normal, foreign], field_domain=field_domain) + + +@pytest.mark.parametrize('mutation', ['missing', 'widened', 'overlap']) +def test_quota_union_cannot_be_weakened_or_made_ambiguous(mutation): + registry = {'vocabularies': { + 'effective_action': {'values': ['normal_run']}, + 'agent_scope_frontier_action': {'values': ['agent_scope_wait']}, + }, 'relations': {'shared_field_names': [{'field': 'effective_action', 'slots': [{ + 'slot': 'should_run.effective_action', + 'vocabularies': ['effective_action', 'agent_scope_frontier_action'], + }]}]}} + assert quota_action_domain(registry) == {'normal_run', 'agent_scope_wait'} + if mutation == 'missing': + registry['relations']['shared_field_names'] = [] + elif mutation == 'widened': + registry['relations']['shared_field_names'][0]['slots'][0]['vocabularies'].append('lease_action') + else: + registry['vocabularies']['agent_scope_frontier_action']['values'].append('normal_run') + with pytest.raises(ValueError, match='anchored|disjoint'): + quota_action_domain(registry) + + +def vocabulary(): + return {'values': ['run', 'wait'], 'producers': [SITE], 'owners': {'python': None}, + 'literal_scan': {'field': 'action'}} + + +def row(value, *, site=SITE, unresolved=False): + return Production(site, 1, 'return', frozenset([value]) if value else frozenset(), unresolved) + + +def test_owner_values_never_satisfy_production_liveness(): + with pytest.raises(ValueError, match='no observed producer'): + validate_production('action', vocabulary(), [row('run')]) + + +def test_undefined_producer_value_is_rejected(): + with pytest.raises(ValueError, match='unregistered values'): + validate_production('action', vocabulary(), [row('run'), row('typo')]) + + +def test_registering_an_unrelated_function_does_not_cover_a_writer(): + with pytest.raises(ValueError, match='undeclared producer sites'): + validate_production('action', vocabulary(), [row('run'), row('wait', site=SITE.replace('emit', 'hidden'))]) + + +def test_dynamic_path_remains_visible_and_cannot_supply_missing_value(): + with pytest.raises(ValueError, match='no observed producer'): + validate_production('action', vocabulary(), [row('run'), row(None, unresolved=True)]) + unknown = validate_production('action', vocabulary(), [row('run'), row('wait'), row(None, unresolved=True)]) + assert unknown == [SITE + ':1'] + + +def test_compatibility_values_must_have_no_observed_production(): + v = vocabulary() + v['compatibility_only'] = {'wait': {'reason': 'Old reader', 'retirement': 'M1'}} + assert validate_production('action', v, [row('run')]) == [] + with pytest.raises(ValueError, match='compatibility-only values are produced'): + validate_production('action', v, [row('run'), row('wait')]) + + +@pytest.mark.parametrize('source, expected', [ + ("function emit() { return {action: flag === 'condition' ? 'run' : 'wait'}; }", {'run', 'wait'}), + ("function emit() { output['action'] = 'run'; output.action = 'wait'; }", {'run', 'wait'}), + ("function read() { if (p.action === 'run') console.log('action'); }", set()), + ("// action: 'comment'\nconst example = `action: 'example'`;", set()), +]) +def test_typescript_parser_observes_results_not_context(source, expected): + rows = collect_production(ROOT, vocabulary(), [SourceFile('loopx/control_plane/quota/probe.ts', '.ts', source)]) + assert set().union(*(r.values for r in rows)) == expected + + +def test_declared_return_is_scanned_in_real_python_syntax(): + v = vocabulary() + v['return_producers'] = [SITE] + rows = collect_production(ROOT, v, [SourceFile(SITE.split('::')[0], '.py', 'def emit():\n return "unregistered"\n')]) + with pytest.raises(ValueError, match='unregistered'): + validate_production('action', v, rows) + + +@pytest.mark.parametrize('vocabulary_name, module, original', [ + ('turn_route', 'loopx/control_plane/turn_driver/driver.py', 'return LoopXTurnRoute.CONTRACT_ERROR'), + ('loop_disposition', 'loopx/control_plane/turn_driver/loop_controller.py', 'LoopXTurnRoute.READY_FOR_HOST: LoopDisposition.RUN_NOW'), +]) +def test_real_return_producer_rejects_an_unregistered_result(vocabulary_name, module, original): + import json + from loopx.semantics.inventory import load_sources + v = json.loads((ROOT / 'loopx/semantics/vocabulary_v0.json').read_text())['vocabularies'][vocabulary_name] + sources = load_sources(ROOT) + replacement = 'return "unknown_action"' if vocabulary_name == 'turn_route' else 'LoopXTurnRoute.READY_FOR_HOST: "unknown_action"' + found = False + mutated = [] + for source in sources: + if source.path == module: + assert original in source.text + source = SourceFile(source.path, source.suffix, source.text.replace(original, replacement, 1)) + found = True + mutated.append(source) + assert found + with pytest.raises(ValueError, match='producer writes unregistered values'): + validate_production(vocabulary_name, v, collect_production(ROOT, v, mutated)) + + +def test_real_turn_decoder_supplies_typed_input_witnesses(): + from loopx.semantics.production import probe_turn_result_input_domain + values = ['validated_progress', 'validated_completion', 'repair_required', + 'replan_required', 'user_action_required', 'wait', 'iteration_failed', + 'host_failure', 'validation_failed', 'writeback_failed', + 'quota_spend_failed', 'terminal_closeout_failed'] + v = {'values': values, 'input_producer': 'loopx/control_plane/turn_driver/transaction.py::_result_kind'} + rows = probe_turn_result_input_domain(v) + assert {r.form for r in rows} == {'input_witness'} + assert set().union(*(r.values for r in rows)) == set(values) + assert all(not r.unresolved for r in rows) + + +@pytest.mark.parametrize('defect', ['constant_result', 'untyped_result', 'unknown_admitted']) +def test_input_witness_probe_rejects_decoder_contract_regressions(monkeypatch, defect): + from types import SimpleNamespace + from loopx.control_plane.turn_driver import transaction + from loopx.semantics.production import probe_turn_result_input_domain + original = transaction._result_kind + + def defective(value, errors): + if defect == 'constant_result': + return transaction.LoopXTurnResultKind.WAIT + if defect == 'untyped_result': + return SimpleNamespace(value=value) + if value == 'unknown_result_kind': + return transaction.LoopXTurnResultKind.WAIT + return original(value, errors) + + monkeypatch.setattr(transaction, '_result_kind', defective) + v = {'values': ['repair_required'], 'input_producer': 'loopx/control_plane/turn_driver/transaction.py::_result_kind'} + with pytest.raises(ValueError, match='decoder'): + probe_turn_result_input_domain(v) + + +def test_legacy_lease_values_stay_visible_without_claiming_production(): + import json + from loopx.semantics.inventory import load_sources + v = json.loads((ROOT / 'loopx/semantics/vocabulary_v0.json').read_text())['vocabularies']['lease_action'] + assert v['status'] == 'legacy' + assert v['producers'] == [] + assert set(v['compatibility_only']) == {'acquire', 'renew', 'transfer', 'release'} + rows = collect_production(ROOT, v, load_sources(ROOT)) + assert not any(r.values for r in rows) + assert validate_production('lease_action', v, rows) == [] + + +def test_new_lease_producer_invalidates_compatibility_only_claim(): + import json + from loopx.semantics.inventory import load_sources + v = json.loads((ROOT / 'loopx/semantics/vocabulary_v0.json').read_text())['vocabularies']['lease_action'] + sources = load_sources(ROOT) + [SourceFile('loopx/control_plane/coordination/new_writer.py', '.py', + 'from .authority_core import LeaseAction\ndef emit():\n return LeaseAction.ACQUIRE\n')] + with pytest.raises(ValueError, match='compatibility-only values are produced'): + validate_production('lease_action', v, collect_production(ROOT, v, sources)) + + +def test_typescript_syntax_failure_reports_only_source_location(): + source = SourceFile('loopx/control_plane/quota/broken.ts', '.ts', 'const secret = "fixture-only";\nfunction invalid( {') + with pytest.raises(ValueError, match=r'broken.ts:2: invalid TypeScript source') as error: + collect_production(ROOT, vocabulary(), [source]) + assert 'fixture-only' not in str(error.value) + + +@pytest.mark.parametrize('suffix, text', [ + ('.py', 'def project(value):\n return {"action": value}\n'), + ('.ts', 'function project(value) { return {action: value}; }'), +]) +def test_generic_effect_files_are_scanned_without_widening_to_all_control_plane(suffix, text): + effect = 'loopx/control_plane/effect_program' + suffix + sibling = 'loopx/control_plane/unrelated' + suffix + rows = collect_production(ROOT, vocabulary(), [SourceFile(effect, suffix, text), SourceFile(sibling, suffix, text)]) + assert {r.site.split('::')[0] for r in rows} == {effect} + assert all(r.unresolved and not r.values for r in rows) + with pytest.raises(ValueError, match='no observed producer'): + validate_production('action', vocabulary(), rows) + + +@pytest.mark.parametrize('suffix,text', [ + ('.ts', 'function turn() { return {action: null}; }\nfunction quota(input) { return {action: input.action}; }'), + ('.py', 'def turn():\n return {"action": None}\ndef quota(value):\n return {"action": value}\n'), +]) +def test_effect_null_and_quota_passthrough_have_distinct_evidence(suffix, text): + rows = collect_production(ROOT, vocabulary(), [SourceFile( + 'loopx/control_plane/effect_program' + suffix, suffix, text, + )]) + assert {r.site.rsplit('::', 1)[1]: (r.values, r.unresolved) for r in rows} == { + 'turn': (frozenset(), False), 'quota': (frozenset(), True), + } + + +def test_real_effect_adapters_are_observed_as_unresolved_passthrough(): + import json + from loopx.semantics.inventory import load_sources + + v = json.loads((ROOT / 'loopx/semantics/vocabulary_v0.json').read_text())['vocabularies']['effective_action'] + paths = {'loopx/control_plane/effect_program.py', 'loopx/control_plane/effect_program.ts'} + owner = v['owners']['python'].split('::')[0] + sources = [source for source in load_sources(ROOT) if source.path in paths | {owner}] + rows = collect_production(ROOT, v, sources) + assert {r.site.split('::')[0] for r in rows} == paths + assert not set().union(*(r.values for r in rows)) + assert {r.site.split('::')[0] for r in rows if r.unresolved} == paths + + +def test_explicit_call_builder_metadata_binds_only_actual_tracked_parameters(): + v = vocabulary() + v['owners']['python'] = 'loopx/control_plane/quota/owner.py::Action' + v['call_producers'] = {'loopx/control_plane/quota/builder.py::emit': ['verdict']} + sources = [ + SourceFile('loopx/control_plane/quota/owner.py', '.py', 'class Action:\n RUN="run"\n WAIT="wait"\n'), + SourceFile('loopx/control_plane/quota/builder.py', '.py', 'def emit(verdict, *, reason):\n return {"verdict": verdict}\n'), + SourceFile(SITE.split('::')[0], '.py', 'from .owner import Action\nfrom .builder import emit as output\ndef emit():\n output(Action.RUN, reason=Action.WAIT)\n'), + ] + rows = collect_production(ROOT, v, sources) + assert set().union(*(r.values for r in rows)) == {'run'} + v['call_producers']['loopx/control_plane/quota/builder.py::emit'] = ['nonexistent'] + with pytest.raises(ValueError, match='builder signature'): + collect_production(ROOT, v, sources) + + +def test_registered_consumer_cannot_replace_a_removed_writer(): + v = vocabulary() + owner = 'loopx/control_plane/quota/owner.py' + v['owners']['python'] = owner + '::Action' + sources = [ + SourceFile(owner, '.py', 'class Action:\n RUN="run"\n WAIT="wait"\n'), + SourceFile(SITE.split('::')[0], '.py', + 'from .owner import Action\ndef emit(packet):\n choices = (Action.RUN, Action.WAIT)\n return predicate(packet, choices)\n'), + ] + with pytest.raises(ValueError, match='no observed producer'): + validate_production('action', v, collect_production(ROOT, v, sources)) diff --git a/tests/architecture/test_semantic_python_production.py b/tests/architecture/test_semantic_python_production.py new file mode 100644 index 0000000000..85c6f96e0c --- /dev/null +++ b/tests/architecture/test_semantic_python_production.py @@ -0,0 +1,278 @@ +"""Semantic counterexamples for the bounded producer observation relation.""" +from __future__ import annotations + +import pytest + +from loopx.semantics.inventory import SourceFile +from loopx.semantics.python_production import scan_python_production + +OWNER = 'loopx/quota/owner.py::Action' +ENUMS = {OWNER: {'RUN': 'run', 'WAIT': 'wait'}} + + +def scan(text, *, returns=(), path='loopx/quota/client.py', calls=None, paths=None): + return scan_python_production(SourceFile(path, '.py', text), field='action', enums=ENUMS, + return_functions=frozenset(returns), call_arguments=calls, return_paths=paths) + + +def known(rows): + return set().union(*(r.values for r in rows if r.form != 'keyword_unproved')) + + +def test_owner_definition_does_not_produce_values(): + assert known(scan('class Action:\n RUN = "run"\n WAIT = "wait"\n', path=OWNER.split('::')[0])) == set() + + +def test_aliased_import_enum_return_and_keyword_produce_values(): + rows = scan('from .owner import Action as A\ndef emit():\n p = Packet(action=A.RUN.value)\n return A.WAIT\n', + calls={'loopx/quota/client.py::Packet': {'action': 0}}) + assert known(rows) == {'run', 'wait'} + assert {r.site for r in rows} == {'loopx/quota/client.py::emit'} + + +def test_local_owner_use_counts_but_definition_does_not(): + rows = scan('class Action:\n RUN = "run"\n WAIT = "wait"\ndef emit():\n return Action.RUN.value\n', path=OWNER.split('::')[0]) + assert known(rows) == {'run'} + + +def test_comparison_and_read_keys_are_not_production(): + rows = scan('from .owner import Action\ndef read(p):\n if p["action"] == Action.RUN.value:\n return p.get("action")\n') + assert known(rows) == set() + + +def test_registered_return_function_includes_only_its_own_returns(): + rows = scan('def emit(flag):\n def inner():\n return "inner"\n return "left" if flag == "condition" else "right"\n', returns=['emit']) + assert known(rows) == {'left', 'right'} + assert {r.site for r in rows} == {'loopx/quota/client.py::emit'} + + +def test_single_local_variable_and_reassignment_boundary(): + rows = scan('def emit(flag):\n code = "run" if flag else "wait"\n return code\n', returns=['emit']) + assert known(rows) == {'run', 'wait'} + assert not any(r.unresolved for r in rows) + rows = scan('def emit(flag):\n code = "run"\n if flag:\n code = dynamic()\n return code\n', returns=['emit']) + assert known(rows) == set() + assert rows[0].unresolved + + +def test_parameter_shadowing_does_not_borrow_owner_values(): + rows = scan('from .owner import Action\ndef emit(Action):\n return Action.RUN.value\n', returns=['emit']) + assert known(rows) == set() + assert rows[0].unresolved + + +def test_same_name_import_from_wrong_module_is_unknown(): + rows = scan('from .unrelated import Action\ndef emit():\n return Action.RUN.value\n', returns=['emit']) + assert known(rows) == set() + assert rows[0].unresolved + + +def test_unknown_member_fails_with_location(): + with pytest.raises(ValueError, match=r'client.py:3: unknown owner member MISSING'): + scan('from .owner import Action\ndef emit():\n return Action.MISSING.value\n') + + +def test_unresolved_result_preserves_conditional_literal_evidence(): + rows = scan('def emit(flag):\n return "run" if flag else dynamic()\n', returns=['emit']) + assert known(rows) == {'run'} + assert rows[0].unresolved + + +def test_field_write_sites_are_attributed_to_distinct_functions(): + rows = scan('def first():\n return {"action": "run"}\ndef second():\n return Packet(action="wait")\n', + calls={'loopx/quota/client.py::Packet': {'action': 0}}) + assert {(r.site, tuple(r.values)) for r in rows} == { + ('loopx/quota/client.py::first', ('run',)), + ('loopx/quota/client.py::second', ('wait',)), + } + + +def test_local_import_shadowing_does_not_borrow_owner_values(): + rows = scan('from .owner import Action\ndef emit():\n from .unrelated import Action\n return Action.RUN.value\n', returns=['emit']) + assert known(rows) == set() + assert rows[0].unresolved + + +def test_assignment_after_return_is_not_a_variable_definition(): + rows = scan('def emit():\n return code\n code = "run"\n', returns=['emit']) + assert known(rows) == set() + assert rows[0].unresolved + + +def test_module_rebind_of_builtin_str_is_unknown(): + rows = scan('str = custom\ndef emit():\n return str("run")\n', returns=['emit']) + assert known(rows) == set() + assert rows[0].unresolved + + +def test_enum_used_only_as_mapping_key_does_not_produce_that_enum(): + rows = scan('from .owner import Action\ndef explain(value):\n reasons = {Action.RUN: "text"}\n return reasons[value]\n') + assert known(rows) == set() + + +def test_enum_comparison_inside_result_packet_does_not_produce_operand(): + rows = scan('from .owner import Action\ndef explain(value):\n packet = {"ok": value == Action.RUN}\n return packet\n') + assert known(rows) == set() + + +def test_dictionary_lookup_result_includes_values_not_keys(): + rows = scan('from .owner import Action\ndef route(value):\n return {"x": Action.RUN, "y": Action.WAIT}[value]\n', returns=['route']) + assert known(rows) == {'run', 'wait'} + assert any(row.unresolved for row in rows) + + +def test_local_enum_dispatch_table_is_a_consumer_not_a_producer(): + from loopx.semantics.production import validate_production + + rows = scan('from .owner import Action\ndef is_quiet(packet):\n choices = (Action.RUN.value, Action.WAIT.value)\n return packet.get("action") in choices\n') + assert known(rows) == set() + with pytest.raises(ValueError, match='no observed producer'): + validate_production('action', {'values': ['run', 'wait'], 'producers': []}, rows) + + +def test_local_enum_container_can_feed_a_real_scalar_write(): + rows = scan('from .owner import Action\ndef emit():\n choices = (Action.RUN.value, Action.WAIT.value)\n return {"action": choices[1]}\n') + assert known(rows) == {'wait'} + assert {r.form for r in rows} == {'dict'} + + +@pytest.mark.parametrize('container', [ + '(Action.RUN.value, Action.WAIT.value)', + '{Action.RUN.value, Action.WAIT.value}', + '{"first": Action.RUN.value, "second": Action.WAIT.value}', +]) +@pytest.mark.parametrize('use', ['return predicate(choices)', 'predicate(allowed=choices)', + 'return packet.get("action") in choices']) +def test_enum_containers_only_used_by_predicates_cannot_establish_liveness(container, use): + rows = scan(f'from .owner import Action\ndef consume(packet):\n choices = {container}\n {use}\n') + assert known(rows) == set() + + +@pytest.mark.parametrize('expression', [ + 'predicate((Action.RUN, Action.WAIT))', + 'predicate(allowed={Action.RUN, Action.WAIT})', + 'predicate(allowed={"x": Action.RUN})', + 'predicate(Action.RUN)', + 'predicate(action=(Action.RUN, Action.WAIT))', +]) +def test_arbitrary_calls_are_not_enum_output_builders(expression): + assert known(scan(f'from .owner import Action\ndef consume():\n return {expression}\n')) == set() + + +@pytest.mark.parametrize('container,index,expected', [ + ('(Action.RUN.value, Action.WAIT.value)', '1', {'wait'}), + ('[Action.RUN.value, Action.WAIT.value]', '-1', {'wait'}), + ('{"first": Action.RUN.value, "second": Action.WAIT.value}', '"first"', {'run'}), +]) +def test_local_container_aliases_resolve_only_selected_output_elements(container, index, expected): + rows = scan(f'from .owner import Action\ndef emit():\n choices = {container}\n alias = choices\n return {{"action": alias[{index}]}}\n') + assert known(rows) == expected + assert not any(r.unresolved for r in rows) + + +def test_enum_container_written_as_a_scalar_is_unknown_not_two_produced_actions(): + rows = scan('from .owner import Action\ndef emit():\n choices = (Action.RUN, Action.WAIT)\n return {"action": choices}\n') + assert known(rows) == set() + assert rows and all(row.unresolved for row in rows) + + +def test_returned_tuple_needs_an_explicit_scalar_output_path(): + # Use a neutral local name: an assignment named `action` is itself one of + # the scanner's explicitly supported field-write forms. + text = 'from .owner import Action\ndef emit():\n selected = Action.RUN.value\n return selected, "reason"\n' + assert known(scan(text)) == set() + rows = scan(text, returns=['emit'], paths={'emit': (0,)}) + assert known(rows) == {'run'} + + +def test_returning_allowed_values_does_not_witness_scalar_liveness(): + from loopx.semantics.production import validate_production + + rows = scan('from .owner import Action\ndef allowed():\n return Action.RUN.value, Action.WAIT.value\n') + assert known(rows) == set() + with pytest.raises(ValueError, match='no observed producer'): + validate_production('action', {'values': ['run', 'wait'], 'producers': []}, rows) + + +def test_field_named_predicate_keywords_cannot_supply_liveness(): + from loopx.semantics.production import validate_production + + rows = scan('from .owner import Action\ndef query():\n return predicate(action=Action.RUN.value) or predicate(action=Action.WAIT.value)\n') + assert known(rows) == set() + assert rows and all(row.unresolved for row in rows) + with pytest.raises(ValueError, match='no observed producer'): + validate_production('action', {'values': ['run', 'wait'], 'producers': []}, rows) + # Still retain the literal's closedness evidence when its output role is unknown. + rows = scan('def query():\n return predicate(action="unregistered")\n') + with pytest.raises(ValueError, match='unregistered values'): + validate_production('action', {'values': ['run'], 'producers': []}, rows) + + +def test_explicit_output_builder_tracks_import_alias_and_only_declared_argument(): + rows = scan('from .owner import Action\nfrom .builder import emit as build\ndef run():\n build(Action.RUN, context=Action.WAIT)\n', + calls={'loopx/quota/builder.py::emit': {'verdict': 0}}) + assert known(rows) == {'run'} + assert {r.form for r in rows} == {'call_argument'} + + +@pytest.mark.parametrize('prefix,parameters,body', [ + ('from .wrong import emit', '', 'emit(Action.RUN)'), + ('from .builder import emit', 'emit', 'emit(Action.RUN)'), + ('from .builder import emit\nemit = predicate', '', 'emit(Action.RUN)'), + ('from .builder import emit', '', 'emit = predicate\n emit(Action.RUN)'), + ('from .builder import emit', '', 'from .wrong import emit\n emit(Action.RUN)'), +]) +def test_wrong_or_shadowed_builder_cannot_borrow_output_evidence(prefix, parameters, body): + rows = scan(f'from .owner import Action\n{prefix}\ndef run({parameters}):\n {body}\n', + calls={'loopx/quota/builder.py::emit': {'verdict': 0}}) + assert known(rows) == set() + + +def test_known_and_unknown_builder_arguments_preserve_closedness_evidence(): + rows = scan('from .builder import emit\ndef run(flag):\n emit("typo" if flag else dynamic())\n', + calls={'loopx/quota/builder.py::emit': {'verdict': 0}}) + assert known(rows) == {'typo'} + assert rows[0].unresolved + + +def test_exhaustive_branch_selection_produces_only_at_output(): + body = ('from .owner import Action\ndef emit(flag):\n' + ' if flag:\n choice = Action.RUN\n else:\n choice = Action.WAIT\n') + assert known(scan(body + ' return {"action": choice.value}\n')) == {'run', 'wait'} + assert known(scan(body + ' return predicate(choice)\n')) == set() + + +def test_declared_return_path_does_not_borrow_sibling_values(): + rows = scan('def emit():\n packet = {"route": {"kind": "run"}, "diagnostic": "not-an-action"}\n return packet\n', + returns=['emit'], paths={'emit': ('route', 'kind')}) + assert known(rows) == {'run'} + assert not any(r.unresolved for r in rows) + + +def test_dynamic_lookup_retains_known_possibilities_and_unknown_boundary(): + rows = scan('from .owner import Action\ndef emit(index):\n choices = (Action.RUN, dynamic())\n return {"action": choices[index]}\n') + assert known(rows) == {'run'} + assert rows[0].unresolved + + +@pytest.mark.parametrize('mutation', ['choices[0] = dynamic()', 'alias[0] = dynamic()', + 'choices.clear()', 'predicate(choices)']) +def test_mutated_or_escaped_local_container_does_not_reuse_stale_elements(mutation): + rows = scan('from .owner import Action\ndef emit():\n choices = [Action.RUN]\n alias = choices\n ' + + mutation + '\n return {"action": alias[0]}\n') + assert known(rows) == set() + assert rows and all(row.unresolved for row in rows) + + +@pytest.mark.parametrize('index', ['99', '"not-an-index"', '1:', 'None']) +def test_non_scalar_or_invalid_literal_lookup_cannot_produce_scalar_action(index): + rows = scan(f'from .owner import Action\ndef emit():\n choices = (Action.RUN, Action.WAIT)\n return {{"action": choices[{index}]}}\n') + assert known(rows) == set() + assert rows[0].unresolved + + +@pytest.mark.parametrize('value', ['"run"', 'Action.RUN.value']) +def test_value_attribute_requires_an_enum_object_not_a_serialized_string(value): + rows = scan(f'from .owner import Action\ndef emit():\n choice = {value}\n return {{"action": choice.value}}\n') + assert known(rows) == set() + assert rows[0].unresolved diff --git a/tests/architecture/test_semantic_vocabulary_drift.py b/tests/architecture/test_semantic_vocabulary_drift.py index 2d51dadff7..760c38d9aa 100644 --- a/tests/architecture/test_semantic_vocabulary_drift.py +++ b/tests/architecture/test_semantic_vocabulary_drift.py @@ -32,7 +32,7 @@ def test_semantic_vocabulary_registry_matches_the_code() -> None: check=False, ) assert completed.returncode == 0, ( - "semantic vocabulary drift smoke failed; the registry, the inventory, or an " + "semantic vocabulary drift smoke failed; the registry, computed inventory, or an " "anchor no longer matches the code:\n" + completed.stdout + completed.stderr ) assert completed.stdout.startswith("semantic-vocabulary-drift-smoke: ok"), ( @@ -83,3 +83,188 @@ def test_candidate_decisions_are_exhaustive_and_default_to_unknown() -> None: registry["formal_model"]["candidate_decisions"]["default"] = "reuse_existing" with pytest.raises(smoke["Drift"], match="default unresolved candidates"): smoke["check_formal_model"](registry["formal_model"]) + + +def test_bounded_producer_scan_rejects_unregistered_write() -> None: + smoke = runpy.run_path(str(SMOKE)) + source = smoke["SourceFile"]( + "loopx/control_plane/quota/probe.py", + ".py", + 'def produce():\n return {"effective_action": "unregistered_action"}\n', + ) + with pytest.raises(smoke["Drift"], match="unregistered_action"): + smoke["check_producers"]( + { + "relations": {"shared_field_names": [{"field": "effective_action", "slots": [{ + "slot": "should_run.effective_action", + "vocabularies": ["effective_action", "agent_scope_frontier_action"], + }]}]}, + "vocabularies": { + "agent_scope_frontier_action": {"values": ["frontier_wait"]}, + "effective_action": { + "tier": "kernel", + "owners": {"python": None, "typescript": None}, + "values": ["registered_action"], + "producers": ["loopx/control_plane/quota/probe.py::produce"], + "literal_scan": {"field": "effective_action", "roots": ["loopx"], "suffixes": [".py"]}, + } + } + }, + [source], + ) + + +def test_bounded_producer_scan_does_not_treat_consumer_reads_as_writes() -> None: + smoke = runpy.run_path(str(SMOKE)) + source = smoke["SourceFile"]( + "loopx/control_plane/quota/probe.py", + ".py", + 'def consume(payload):\n return payload.get("effective_action") == "registered_action"\n', + ) + assert smoke["_producer_literals"]("effective_action", source) == set() + + +@pytest.mark.parametrize('suffix, text, expected', [ + ('.py', 'effective_action == Action.NORMAL.value or state == "not_an_action"', set()), + ('.ts', 'effective_action === Action.NORMAL || state === "not_an_action";', set()), + ('.py', '# effective_action = "comment"\nexample = \'effective_action == "example"\'', set()), + ('.ts', '// effective_action = "comment"\nconst example = \'effective_action === "example"\';', set()), + ('.py', 'effective_action = "run" if state == "condition" else "wait"', {'run', 'wait'}), + ('.ts', 'effective_action = state === "condition" ? "run" : "wait";', {'run', 'wait'}), + ('.py', 'str(packet.get("effective_action") or "") in {"run", "wait"}', {'run', 'wait'}), + ('.ts', '["run", "wait"].includes(packet.effective_action);', {'run', 'wait'}), + ('.py', 'match packet["effective_action"]:\n case "run" | "wait": pass', {'run', 'wait'}), + ('.ts', 'switch (packet.effective_action) { case "run": break; case "wait": break; }', {'run', 'wait'}), + ('.ts', 'const packet = {["effective_action"]: "run"};', {'run'}), + ('.ts', 'const packet = {[`effective_action`]: "run"};', {'run'}), + ('.ts', 'const packet = {[effective_action]: "not_a_static_key"};', set()), + ('.py', '(p.get("effective_action") and p["state"]) == "eligible"', set()), + ('.py', '(p.get("effective_action") or p["state"]) == "eligible"', set()), + ('.ts', '(packet.effective_action || packet.state) === "eligible";', set()), + ('.ts', '(packet.effective_action ?? "") === "run";', {'run'}), +]) +def test_literal_uses_belong_to_the_field_not_neighboring_syntax(suffix, text, expected): + smoke = runpy.run_path(str(SMOKE)) + source = smoke['SourceFile']('loopx/probe' + suffix, suffix, text) + assert set(smoke['scan_literals']('effective_action', ['loopx'], [suffix], [source])) == expected + + +@pytest.mark.parametrize('value', ['normal_run', 'agent_scope_wait']) +@pytest.mark.parametrize('suffix', ['.py', '.ts']) +def test_registered_root_action_literals_still_require_owner_import(value, suffix): + smoke = runpy.run_path(str(SMOKE)) + source = smoke['SourceFile']('loopx/probe' + suffix, suffix, f'effective_action = "{value}"') + with pytest.raises(smoke['Drift'], match='import EffectiveAction or AgentScopeFrontierAction'): + smoke['check_literal_vocabularies'](smoke['load_registry'](), [source]) + + +def test_real_monitor_membership_cannot_revert_to_bare_action_literals(): + smoke = runpy.run_path(str(SMOKE)) + path = 'loopx/control_plane/quota/monitor_poll_commit.ts' + source = (REPO_ROOT / path).read_text() + old = 'AgentScopeFrontierAction.AGENT_SCOPE_WAIT, EffectiveAction.MONITOR_QUIET_SKIP' + assert old in source + changed = source.replace(old, '"agent_scope_wait", "monitor_quiet_skip"') + with pytest.raises(smoke['Drift'], match='bare action literals'): + smoke['check_literal_vocabularies'](smoke['load_registry'](), [smoke['SourceFile'](path, '.ts', changed)]) + + +@pytest.mark.parametrize('name, metadata, selection', [ + ('effective_action', 'call_producers', ['state']), + ('loop_disposition', 'call_producers', ['state']), + ('agent_scope_frontier_action', 'return_paths', ['action']), + ('turn_route', 'return_paths', ['action']), +]) +def test_registry_cannot_add_unanchored_output_selectors(name, metadata, selection): + smoke = runpy.run_path(str(SMOKE)) + registry = copy.deepcopy(smoke['load_registry']()) + registry['vocabularies'][name].setdefault(metadata, {})[ + 'loopx/control_plane/quota/decision_summary.py::quota_effective_action' + ] = selection + with pytest.raises(smoke['Drift'], match='anchored output evidence exactly'): + smoke['check_coverage_floor'](registry) + + +def test_bounded_context_scope_excludes_only_declared_multi_value_fork() -> None: + smoke = runpy.run_path(str(SMOKE)) + registry = smoke["load_registry"]() + sources = smoke["load_sources"](REPO_ROOT) + inventory = smoke["build_inventory"](REPO_ROOT, sources=sources) + assert smoke["check_scope_declarations"](registry, inventory) == 3 + + +def test_bounded_context_scope_requires_every_distinct_defining_module() -> None: + smoke = runpy.run_path(str(SMOKE)) + registry = copy.deepcopy(smoke["load_registry"]()) + registry["scope_declarations"]["SOURCE_SURFACES"]["contexts"] = registry["scope_declarations"]["SOURCE_SURFACES"]["contexts"][:-1] + sources = smoke["load_sources"](REPO_ROOT) + inventory = smoke["build_inventory"](REPO_ROOT, sources=sources) + with pytest.raises(smoke["Drift"], match="every defining module"): + smoke["check_scope_declarations"](registry, inventory) + + +@pytest.mark.parametrize("text, expected", [ + ('payload["effective_action"] = "new_action"', {"new_action"}), + ('route.effective_action: str = "new_action"', {"new_action"}), + ('Packet(effective_action="new_action")', {"new_action"}), + ('payload = {"effective_action":\n "left" if flag == "condition" else "right"}', {"left", "right"}), + ('effective_action = payload.get("effective_action", "fallback")', set()), + ('effective_action == "not_produced"', set()), + ('# effective_action = "comment"', set()), + ('example = \'effective_action = "example"\'', set()), +]) +def test_python_production_forms_separate_result_from_context(text, expected) -> None: + smoke = runpy.run_path(str(SMOKE)) + source = smoke["SourceFile"]("loopx/control_plane/quota/probe.py", ".py", text) + assert smoke["_producer_literals"]("effective_action", source) == expected + + +def test_return_producer_scope_cannot_be_removed_from_registry(): + smoke = runpy.run_path(str(SMOKE)) + registry = copy.deepcopy(smoke['load_registry']()) + registry['vocabularies']['effective_action']['return_producers'] = [] + with pytest.raises(smoke['Drift'], match='RETURN_PRODUCER_ANCHOR'): + smoke['check_coverage_floor'](registry) + + +@pytest.mark.parametrize('name', ['turn_route', 'loop_disposition', 'agent_scope_frontier_action']) +def test_registered_kernel_producer_coverage_cannot_be_removed(name): + smoke = runpy.run_path(str(SMOKE)) + registry = copy.deepcopy(smoke['load_registry']()) + registry['vocabularies'][name].pop('producers') + with pytest.raises(smoke['Drift'], match='PRODUCER_VOCABULARY_ANCHOR'): + smoke['check_coverage_floor'](registry) + + +@pytest.mark.parametrize('metadata,name', [ + ('call_producers', 'loop_disposition'), + ('call_producers', 'agent_scope_frontier_action'), + ('call_producers', 'turn_result_kind'), + ('return_paths', 'turn_route'), + ('return_paths', 'turn_result_kind'), +]) +def test_explicit_output_evidence_cannot_be_removed_or_redirected(metadata, name): + smoke = runpy.run_path(str(SMOKE)) + registry = copy.deepcopy(smoke['load_registry']()) + registry['vocabularies'][name][metadata] = {} + with pytest.raises(smoke['Drift'], match='anchored output evidence'): + smoke['check_coverage_floor'](registry) + + +@pytest.mark.parametrize("legacy_report", [None, "not even JSON"]) +def test_live_inventory_ignores_missing_or_stale_reports(tmp_path, monkeypatch, legacy_report): + smoke = runpy.run_path(str(SMOKE)) + registry = smoke["load_registry"]() + sources = smoke["load_sources"](REPO_ROOT) + if legacy_report is not None: + path = tmp_path / "loopx/semantics/inventory_v0.json" + path.parent.mkdir(parents=True) + path.write_text(legacy_report) + monkeypatch.setitem(smoke["check_inventory"].__globals__, "REPO_ROOT", tmp_path) + inventory, _ = smoke["check_inventory"](registry, sources) + assert inventory["summary"]["source_files"] == len(sources) + # A newly observed duplicate must still fail; an old or missing report cannot hide it. + duplicate = [smoke["SourceFile"](f"loopx/q9_{name}.py", ".py", 'Q9_DUPLICATE = "same"\n') + for name in ("first", "second")] + with pytest.raises(smoke["Drift"], match="same_runtime_forks grew"): + smoke["check_inventory"](registry, sources + duplicate) diff --git a/tests/control_plane/test_agent_scope_frontier_contract.py b/tests/control_plane/test_agent_scope_frontier_contract.py new file mode 100644 index 0000000000..fc84400a43 --- /dev/null +++ b/tests/control_plane/test_agent_scope_frontier_contract.py @@ -0,0 +1,200 @@ +"""A frontier owns one action slot; quota actions cannot overwrite its domain.""" + +from __future__ import annotations + +import json +from pathlib import Path +import subprocess +import sys + +import pytest + +from examples.control_plane.quota_plan_fixtures import ( + SCOPED_AGENT_ID, + write_cli_fixture, +) +from loopx.control_plane.agents.agent_scope_frontier import ( + AgentScopeFrontierAction, + build_agent_scope_frontier_payload, +) +from loopx.control_plane.testing.quota_fixtures import ( + quota_status_payload, + quota_todo_item, +) +from loopx.control_plane.work_items.goal_route_hint import build_goal_route_hint +from loopx.quota import build_quota_should_run, render_quota_should_run_markdown + + +def _frontier(action=AgentScopeFrontierAction.AGENT_SCOPE_WAIT, *, extra_fields=None): + return build_agent_scope_frontier_payload( + agent_id="frontier-fixture", + action=action, + quiet_noop_allowed=True, + spend_policy="no spend while waiting", + reason="no runnable candidate", + recommended_action="wait for the prerequisite", + candidate_counts={}, + extra_fields=extra_fields, + ) + + +@pytest.mark.parametrize("action", list(AgentScopeFrontierAction)) +def test_frontier_has_one_canonical_action_slot(action): + payload = _frontier(action) + assert payload["schema_version"] == "agent_scope_frontier_v1" + assert payload["action"] == action.value + assert "effective_action" not in payload + + +@pytest.mark.parametrize( + "extra", + [ + {"action": "normal_run"}, + {"effective_action": "normal_run"}, + {"schema_version": "agent_scope_frontier_v0"}, + {"action": None}, + {"effective_action": "agent_scope_wait"}, + {"schema_version": "agent_scope_frontier_v1"}, + ], +) +def test_extra_fields_cannot_replace_the_frontier_domain(extra): + with pytest.raises(ValueError, match="frontier.*reserved"): + _frontier(extra_fields=extra) + + +def test_frontier_keeps_bounded_candidate_context(): + payload = _frontier(extra_fields={"priority_preemption": True}) + assert payload["priority_preemption"] is True + + +@pytest.mark.parametrize( + "frontier, expected", + [ + ( + { + "schema_version": "agent_scope_frontier_v0", + "effective_action": "agent_scope_wait", + }, + "agent_scope_wait", + ), + ( + { + "schema_version": "agent_scope_frontier_v0", + "action": "agent_scope_wait", + "effective_action": "agent_scope_wait", + }, + "agent_scope_wait", + ), + ( + { + "schema_version": "agent_scope_frontier_v1", + "action": "successor_replan_required", + }, + "successor_replan_required", + ), + ( + { + "schema_version": "agent_scope_frontier_v1", + "action": "successor_replan_required", + "effective_action": "agent_scope_wait", + }, + "successor_replan_required", + ), + ], +) +def test_goal_route_reads_canonical_action_first_and_legacy_as_fallback( + frontier, expected +): + hint = build_goal_route_hint( + agent_identity={"agent_id": "frontier-fixture"}, + agent_todo_summary={}, + agent_lane_next_action=None, + agent_scope_frontier=frontier, + agent_lane_frontier_hint=None, + active_state_next_action="Preserve the goal route.", + latest_run_recommended_action=None, + selected_recommended_action="Wait.", + ) + assert hint["route_decision"] == expected + assert hint["preserves_goal_next_action"] is True + assert hint["goal_next_action_mutation"] == "none" + + +def test_live_should_run_and_markdown_use_the_v1_frontier(): + status = quota_status_payload( + goal_id="frontier-fixture", + status="active", + recommended_action="Complete the claimed prerequisite.", + coordination={ + "agent_model": "peer_v1", + "registered_agents": ["current-agent", "other-agent"], + }, + agent_todo_items=[ + quota_todo_item( + todo_id="todo_prerequisite", + title="Complete the claimed prerequisite.", + claimed_by="other-agent", + ) + ], + ) + guard = build_quota_should_run( + status, goal_id="frontier-fixture", agent_id="current-agent" + ) + frontier = guard["agent_scope_frontier"] + assert frontier["schema_version"] == "agent_scope_frontier_v1" + assert frontier["action"] == "reassignment_required" + assert "effective_action" not in frontier + assert guard["effective_action"] == "reassignment_required" + assert guard["decision"] == "reassignment_required" + assert guard["should_run"] is False + assert guard["normal_delivery_allowed"] is False + assert guard["goal_route_hint"]["route_decision"] == "reassignment_required" + assert ( + "agent_scope_frontier: action=reassignment_required" + in render_quota_should_run_markdown(guard) + ) + + +def test_cli_should_run_reads_disposable_state_and_emits_v1(tmp_path): + registry, runtime, project = write_cli_fixture(tmp_path, scoped_agents=True) + state = project / ".codex/goals/half-speed/ACTIVE_GOAL_STATE.md" + with state.open("a", encoding="utf-8") as stream: + stream.write( + "\n## Agent Todo\n\n" + "- [ ] [P0] Complete the claimed prerequisite.\n" + " \n" + ) + result = subprocess.run( + [ + sys.executable, + "-m", + "loopx.cli", + "--registry", + str(registry), + "--runtime-root", + str(runtime), + "--format", + "json", + "quota", + "should-run", + "--goal-id", + "half-speed", + "--agent-id", + SCOPED_AGENT_ID, + "--runtime-profile", + "outer_controller", + "--scan-path", + str(project), + ], + cwd=Path(__file__).resolve().parents[2], + capture_output=True, + text=True, + check=True, + ) + guard = json.loads(result.stdout) + assert guard["effective_action"] == "reassignment_required" + assert guard["should_run"] is False + assert guard["agent_scope_frontier"]["schema_version"] == "agent_scope_frontier_v1" + assert guard["agent_scope_frontier"]["action"] == "reassignment_required" + assert "effective_action" not in guard["agent_scope_frontier"] diff --git a/tests/control_plane/test_effect_interpreter_packet.py b/tests/control_plane/test_effect_interpreter_packet.py index fe4f0d94f3..c51d1b4f5f 100644 --- a/tests/control_plane/test_effect_interpreter_packet.py +++ b/tests/control_plane/test_effect_interpreter_packet.py @@ -1,12 +1,29 @@ from __future__ import annotations +from copy import deepcopy +from dataclasses import asdict +import json +import sys + +import pytest + from loopx.control_plane.effect_program import ( interpret_quota_should_run_packet, + interpret_turn_result_packet, ) +from loopx.control_plane.effect_runtime import effect_runtime_result +from loopx.control_plane.quota.turn_envelope import build_turn_envelope from loopx.control_plane.scheduler.execution_context import ( scheduler_execution_context_for_runtime_profile, ) from loopx.control_plane.testing.quota_fixtures import quota_status_payload +from loopx.control_plane.turn_driver import ( + build_loopx_turn_plan, + load_loopx_turn_plan_from_journal, + run_loopx_turn_once, + validate_loopx_turn_host_result, +) +from loopx.control_plane.turn_driver.transaction import LoopXTurnResultKind from loopx.quota import build_quota_should_run GOAL_ID = "effect-interpreter-fixture" @@ -256,3 +273,134 @@ def test_effect_turn_carries_scheduler_ack_and_failure_hints() -> None: "--failure", "--execute", ) + + +@pytest.mark.parametrize("result_kind", [kind.value for kind in LoopXTurnResultKind]) +def test_result_runtime_and_python_adapter_expose_verdict_without_action(result_kind): + packet = { + "result_kind": result_kind, + "completed_phases": ["host_execute", "typed_result"], + "failed_phase": "validation", + "next_cli_actions": ["loopx status"], + } + before = deepcopy(packet) + raw = effect_runtime_result("effect.interpret_turn_result", {"packet": packet}) + turn = interpret_turn_result_packet(packet) + assert raw["observation"]["decision"] == turn.observation.decision == result_kind + assert raw["observation"]["effective_action"] is None + assert turn.observation.effective_action is None + assert ( + json.loads(json.dumps(asdict(turn)))["observation"]["effective_action"] is None + ) + assert turn.observation.should_run is False + assert turn.request.context["failed_phase"] == "validation" + assert turn.next_effect.cli_actions == ("loopx status",) + assert packet == before + + +@pytest.mark.parametrize( + "host_action", + [ + "normal_run", + "agent_scope_wait", + "wait", + "foreign_action", + None, + 42, + {"action": "normal_run"}, + ["normal_run"], + ], +) +def test_host_action_cannot_enter_the_quota_slot(host_action): + turn = interpret_turn_result_packet( + {"result_kind": "wait", "effective_action": host_action} + ) + assert turn.observation.decision == "wait" + assert turn.observation.effective_action is None + + +def test_real_executor_uses_verdict_and_replays_persisted_plan(tmp_path): + status = quota_status_payload( + goal_id=GOAL_ID, + status="active", + recommended_action="Advance the bounded slice.", + coordination={"agent_model": "peer_v1", "registered_agents": ["codex-fixture"]}, + agent_todo_items=[ + { + "index": 1, + "todo_id": "todo_effect_fixture", + "text": "[P1] Advance the bounded slice.", + "role": "agent", + "status": "open", + "priority": "P1", + "task_class": "advancement_task", + } + ], + ) + packet = build_quota_should_run( + status, + goal_id=GOAL_ID, + agent_id="codex-fixture", + ) + plan = build_loopx_turn_plan( + build_turn_envelope(packet), + host="generic-cli", + execution_mode="isolated-headless", + ) + result = { + "schema_version": "loopx_turn_result_v0", + "turn_key": plan["transaction"]["turn_key"], + "result_kind": "wait", + "completed_phases": ["host_execute", "typed_result"], + } + # The executor's existing host schema rejects this field before interpretation. + invalid = validate_loopx_turn_host_result( + plan, {**result, "effective_action": "normal_run"} + ) + assert invalid["ok"] is False + assert "unsupported host result fields: effective_action" in invalid["errors"] + wrong_verdict = validate_loopx_turn_host_result( + plan, {**result, "result_kind": "normal_run"} + ) + assert wrong_verdict["ok"] is False + assert "unsupported host result kind" in wrong_verdict["errors"] + result_path = tmp_path / "result.json" + result_path.write_text(json.dumps(result), encoding="utf-8") + + def forbidden_effect(*_args, **_kwargs): + pytest.fail( + "a wait result must not write back, spend, or apply scheduler effects" + ) + + kwargs = { + "host_argv": [ + sys.executable, + "-c", + "import pathlib,sys; print(pathlib.Path(sys.argv[1]).read_text())", + str(result_path), + ], + "project": tmp_path, + "runtime_root": tmp_path / "runtime", + "goal_id": GOAL_ID, + "execute": True, + "timeout_seconds": 5, + "writeback": forbidden_effect, + "spend": forbidden_effect, + "scheduler": forbidden_effect, + } + first = run_loopx_turn_once(plan, **kwargs) + assert first["status"] == "stopped" + assert first["result_kind"] == "wait" + assert first["effects"]["host_invoked"] is True + assert first["effects"]["quota_spent"] is False + resumed = load_loopx_turn_plan_from_journal( + tmp_path / "runtime", + goal_id=GOAL_ID, + turn_key=result["turn_key"], + ) + assert resumed == plan + assert resumed["turn_envelope"]["effective_action"] == packet["effective_action"] + replay = run_loopx_turn_once(resumed, **kwargs) + assert replay["replayed"] is True + assert replay["result_kind"] == "wait" + assert not any(replay["effects"].values()) diff --git a/tests/control_plane/test_frontier_envelope_compatibility.py b/tests/control_plane/test_frontier_envelope_compatibility.py new file mode 100644 index 0000000000..8348eec598 --- /dev/null +++ b/tests/control_plane/test_frontier_envelope_compatibility.py @@ -0,0 +1,193 @@ +"""Frontier wire reduction must not rewrite historical signed Turn plans.""" + +from __future__ import annotations + +from copy import deepcopy +from hashlib import sha256 +import json + +import pytest + +from loopx.control_plane.quota.turn_envelope import ( + build_turn_envelope, + quota_action_signature_document, + turn_envelope_action_signature_document, +) +from loopx.control_plane.agents.agent_scope_frontier import ( + AgentScopeFrontierAction, + build_agent_scope_frontier_payload, +) +from loopx.control_plane.turn_driver.driver import build_loopx_turn_plan +from loopx.control_plane.turn_driver.journal_store import ( + load_loopx_turn_plan_from_journal, + load_turn_journal, + turn_journal_path, + write_turn_journal_checkpoint, +) +from loopx.control_plane.turn_driver.turn_journal_runtime import ( + interpret_turn_journal_projection, +) + + +# Captured before the v1 writer migration. These characterize historical bytes; +# the field-preservation and mutation assertions below define the invariant. +V0_SIGNATURE_HASHES = { + True: "sha256:c6d7977aef90126a9cff9525be407c69dd62152e61fb9cec15687cd54ef0fe5d", + False: "sha256:8f04ecb05ca2156bd59123ad8df6301122552fc62ce79dd7fadce1772f10a5fc", +} + + +def _legacy_decision(*, action_present=True): + frontier = { + "schema_version": "agent_scope_frontier_v0", + "effective_action": "successor_replan_required", + "blocks_delivery": True, + "quiet_noop_allowed": False, + "requires_replan": True, + "recommended_action": "Resolve the ready successor.", + "spend_policy": "spend after validated successor writeback", + } + if action_present: + frontier["action"] = "successor_replan_required" + return { + "ok": True, + "goal_id": "frontier-fixture", + "agent_identity": {"agent_id": "frontier-agent"}, + "decision": "run", + "should_run": True, + "effective_action": "successor_replan_required", + "state": "eligible", + "recommended_action": "Resolve the ready successor.", + "selected_todo": {"todo_id": "todo_successor", "status": "open"}, + "interaction_contract": { + "schema_version": "loopx_interaction_contract_v0", + "mode": "successor_replan_required", + "agent_channel": { + "must_attempt": True, + "delivery_allowed": True, + "quiet_noop_allowed": False, + "primary_action": "Resolve the ready successor.", + }, + "user_channel": {"action_required": False, "notify": "DONT_NOTIFY"}, + "cli_channel": {}, + }, + "agent_scope_frontier": frontier, + } + + +def _signature_hash(document): + return ( + "sha256:" + + sha256( + json.dumps( + document, + ensure_ascii=False, + sort_keys=True, + separators=(",", ":"), + ).encode() + ).hexdigest() + ) + + +@pytest.mark.parametrize("action_present", [True, False]) +def test_v0_frontier_retains_signed_fields(action_present): + source = _legacy_decision(action_present=action_present) + before = deepcopy(source) + envelope = build_turn_envelope(source) + assert source == before + assert ( + envelope["contract_capsule"]["agent_scope_frontier"] + == source["agent_scope_frontier"] + ) + signature = turn_envelope_action_signature_document(envelope) + assert signature == quota_action_signature_document(source) + assert _signature_hash(signature) == envelope["action_signature"]["source_hash"] + assert _signature_hash(signature) == V0_SIGNATURE_HASHES[action_present] + + for key in source["agent_scope_frontier"]: + changed = deepcopy(envelope) + del changed["contract_capsule"]["agent_scope_frontier"][key] + assert ( + _signature_hash(turn_envelope_action_signature_document(changed)) + != V0_SIGNATURE_HASHES[action_present] + ) + + +def test_v1_reduces_only_the_frontier_slot_and_keeps_root_action(): + legacy = _legacy_decision() + source = deepcopy(legacy) + source["agent_scope_frontier"] = build_agent_scope_frontier_payload( + agent_id="frontier-agent", + action=AgentScopeFrontierAction.SUCCESSOR_REPLAN_REQUIRED, + quiet_noop_allowed=False, + requires_replan=True, + candidate_counts={}, + reason="ready successor", + recommended_action="Resolve the ready successor.", + spend_policy="spend after validated successor writeback", + ) + legacy_envelope = build_turn_envelope(legacy) + envelope = build_turn_envelope(source) + capsule = envelope["contract_capsule"]["agent_scope_frontier"] + expected = dict(legacy["agent_scope_frontier"]) + del expected["effective_action"] + expected["schema_version"] = "agent_scope_frontier_v1" + assert capsule == expected + assert ( + envelope["effective_action"] + == legacy_envelope["effective_action"] + == "successor_replan_required" + ) + signature = turn_envelope_action_signature_document(envelope) + legacy_signature = turn_envelope_action_signature_document(legacy_envelope) + assert signature == quota_action_signature_document(source) + assert envelope["action_signature"]["matches"] is True + assert _signature_hash(signature) != _signature_hash(legacy_signature) + # The wire reduction is versioned, signed, and confined to this capsule. + legacy_signature["contract_capsule"]["agent_scope_frontier"] = expected + assert signature == legacy_signature + + +@pytest.mark.parametrize("action_present", [True, False]) +def test_persisted_v0_plan_resumes_without_rewriting_frontier(tmp_path, action_present): + envelope = build_turn_envelope(_legacy_decision(action_present=action_present)) + plan = build_loopx_turn_plan( + envelope, host="generic-cli", execution_mode="isolated-headless" + ) + assert plan["route"]["kind"] == "replan_required" + turn_key = plan["transaction"]["turn_key"] + path = turn_journal_path(tmp_path, goal_id="frontier-fixture", turn_key=turn_key) + journal = { + "schema_version": "loopx_turn_journal_v0", + "goal_id": "frontier-fixture", + "turn_key": turn_key, + "status": "in_progress", + "completed_phases": [], + "plan": plan, + } + write_turn_journal_checkpoint(path, journal) + # Host execution and typed-result validation commit as one checkpoint. + journal["completed_phases"] = ["host_execute", "typed_result"] + write_turn_journal_checkpoint(path, journal) + original_bytes = path.read_bytes() + resumed = load_loopx_turn_plan_from_journal( + tmp_path, goal_id="frontier-fixture", turn_key=turn_key + ) + assert resumed == plan + assert ( + _signature_hash( + turn_envelope_action_signature_document(resumed["turn_envelope"]) + ) + == V0_SIGNATURE_HASHES[action_present] + ) + inspection = interpret_turn_journal_projection( + load_turn_journal(path), + goal_id="frontier-fixture", + agent_id="frontier-agent", + turn_key=turn_key, + ) + assert inspection["journal_consistent"] is True + assert inspection["recovery_decision"]["can_continue"] is True + assert inspection["recovery_decision"]["resume_from"] == "validation" + assert inspection["recovery_decision"]["reinvoke_host"] is False + assert path.read_bytes() == original_bytes diff --git a/tests/control_plane/test_quota_settlement_cli.py b/tests/control_plane/test_quota_settlement_cli.py index c4eddcac93..6d9b4f6453 100644 --- a/tests/control_plane/test_quota_settlement_cli.py +++ b/tests/control_plane/test_quota_settlement_cli.py @@ -2783,6 +2783,8 @@ def test_agent_selection_rejects_unprojected_todo(tmp_path: Path) -> None: assert invalid_rc != 0, invalid assert invalid["ok"] is False assert invalid["error_code"] == "quota_action_selection_rejected" + assert invalid["effective_action"] == "quota_skip" + assert invalid["should_run"] is False assert invalid["action_selection_qualification"] == { "schema_version": "action_selection_qualification_v0", "state": "rejected", @@ -3154,6 +3156,8 @@ def test_selection_added_after_pending_guard_reports_final_boundary( assert rejected_rc == 1, rejected assert rejected["error_code"] == "quota_action_selection_deferred" + assert rejected["effective_action"] == "quota_skip" + assert rejected["should_run"] is False assert rejected["action_selection_qualification"]["reason"] == "control_repair" assert rejected["action_selection_qualification"]["requested_todo_id"] == ( added["todo_id"] diff --git a/tests/control_plane/test_user_gate_lane_progress.py b/tests/control_plane/test_user_gate_lane_progress.py index d6bb8af290..bc42c31c8e 100644 --- a/tests/control_plane/test_user_gate_lane_progress.py +++ b/tests/control_plane/test_user_gate_lane_progress.py @@ -3,6 +3,9 @@ from datetime import datetime, timedelta, timezone from pathlib import Path +import pytest + +from loopx.control_plane.todos.user_gate import apply_scoped_user_gate_fallback_projection from loopx.control_plane.quota.scheduler_ack import ( record_quota_scheduler_ack_for_decision, ) @@ -32,6 +35,30 @@ ) +@pytest.mark.parametrize("action", ["quota_skip", "monitor_quiet_skip", None]) +def test_runnable_user_gate_fallback_replaces_a_canonical_skip_action(action): + original = {"decision": "skip", "should_run": False, "effective_action": action} + result = apply_scoped_user_gate_fallback_projection( + original, fallback={"recommended_action": "advance non-gated work"}, + replan_decision_allowed=False, + ) + assert result["effective_action"] == "scoped_user_gate_fallback" + assert result["should_run"] is True + assert original["should_run"] is False + + +def test_user_gate_fallback_preserves_repair_and_replan_precedence(): + original = {"decision": "run", "should_run": True, "effective_action": "capability_bridge_repair"} + fallback = {"recommended_action": "advance non-gated work"} + result = apply_scoped_user_gate_fallback_projection( + original, fallback=fallback, replan_decision_allowed=False, + ) + assert result["effective_action"] == "capability_bridge_repair" + assert apply_scoped_user_gate_fallback_projection( + original, fallback=fallback, replan_decision_allowed=True, + ) is original + + def _status_payload(*, gate_action_kind: str, blocks_deferred: bool = False) -> dict: completed = quota_todo_item( todo_id="todo_prerequisite", diff --git a/tests/control_plane_ts/effect_program.test.ts b/tests/control_plane_ts/effect_program.test.ts index 0af8caf531..1a54fef53b 100644 --- a/tests/control_plane_ts/effect_program.test.ts +++ b/tests/control_plane_ts/effect_program.test.ts @@ -4,6 +4,8 @@ import test from "node:test"; import { commitStepPayload, effectProgramFromOrderedSteps, + interpretQuotaShouldRunPacket, + interpretTurnResultPacket, requireMatchingEffectId, seedCommittedSteps, settlementBindGate, @@ -45,6 +47,74 @@ const invalidSettlementResult: SettlementResult<{ value: number }> = { }; void invalidSettlementResult; +test("Turn-result verdicts and host actions never become quota actions", () => { + for (const resultKind of ["validated_progress", "repair_required", "wait", "host_failure"]) { + for (const hostAction of [undefined, null, "", "normal_run", "agent_scope_wait", "wait", "foreign_action", 42, { action: "normal_run" }]) { + const packet = { + result_kind: resultKind, + effective_action: hostAction, + failed_phase: "validation", + completed_phases: ["host_execute", "typed_result"], + next_cli_actions: ["loopx status"], + scheduler_hint: { + action: "apply_rrule", cadence_class: "repair", + codex_app: { + ack_hint: { cli_args: ["quota", "scheduler-ack-current"] }, + failure_hint: { cli_args: ["quota", "scheduler-ack-current", "--failure"] }, + }, + }, + }; + const before = structuredClone(packet); + const turn = interpretTurnResultPacket(packet); + const noAction: null = turn.observation.effective_action; + // @ts-expect-error result observations cannot hold even a valid quota action + const foreignAction: typeof noAction = "normal_run"; + void foreignAction; + assert.equal(noAction, null); + assert.equal(turn.observation.decision, resultKind); + assert.equal(turn.observation.should_run, false); + assert.equal(turn.request.context.failed_phase, "validation"); + assert.deepEqual(turn.next_effect, { + cli_actions: ["loopx status"], execution_mode: null, + scheduler_action: "apply_rrule", cadence_class: "repair", + ack_cli_args: ["quota", "scheduler-ack-current"], + failure_cli_args: ["quota", "scheduler-ack-current", "--failure"], + }); + assert.equal(JSON.parse(JSON.stringify(turn)).observation.effective_action, null); + assert.deepEqual(packet, before); + } + } +}); + +test("quota observations retain their string action and ignore host verdict fields", () => { + for (const action of ["normal_run", "quota_skip", "agent_scope_wait", "successor_replan_required"]) { + const turn = interpretQuotaShouldRunPacket({ + decision: "run", should_run: true, effective_action: action, result_kind: "wait", + interaction_contract: { + schema_version: "loopx_interaction_contract_v0", mode: "bounded_delivery", + user_channel: { action_required: false, notify: "DONT_NOTIFY" }, + agent_channel: { must_attempt: true, delivery_allowed: true, quiet_noop_allowed: false }, + cli_channel: {}, + }, + }); + const quotaAction: string = turn.observation.effective_action; + assert.equal(quotaAction, action); + assert.equal(turn.observation.decision, "run"); + assert.equal(turn.observation.should_run, true); + } + assert.throws(() => interpretQuotaShouldRunPacket({}), /interaction_contract must be an object/); +}); + +test("missing or malformed result packets cannot manufacture an action", () => { + for (const packet of [undefined, null, [], "wait", {}, { effective_action: "normal_run" }]) { + const turn = interpretTurnResultPacket(packet); + assert.equal(turn.observation.effective_action, null); + assert.equal(turn.observation.decision, ""); + assert.equal(turn.observation.should_run, false); + assert.deepEqual(turn.next_effect.cli_actions, []); + } +}); + test("ordered Effect Program steps preserve data and skip malformed entries", () => { const program = effectProgramFromOrderedSteps( [ diff --git a/tests/control_plane_ts/turn_journal.test.ts b/tests/control_plane_ts/turn_journal.test.ts index 4ad75accee..1bd362c81e 100644 --- a/tests/control_plane_ts/turn_journal.test.ts +++ b/tests/control_plane_ts/turn_journal.test.ts @@ -112,13 +112,17 @@ test("legal terminal replay is projected without effects or private fields", () assert.deepEqual(input, before); }); -test("journal interpretation preserves the canonical Effect Program slots", () => { +test("journal replay uses its own decision without a quota action slot", () => { const turn = interpretTurnJournalEffect(request()); assert.equal(turn.request.kind, "turn_journal"); assert.equal(turn.interpretation.route, "turn_journal_replay"); assert.equal(turn.observation.decision, "replay_legal"); assert.equal(turn.observation.should_run, false); + assert.equal("effective_action" in turn.observation, false); + const blocked = interpretTurnJournalEffect({...request(), agent_id: "other-agent"}); + assert.equal(blocked.observation.decision, "replay_blocked"); + assert.equal("effective_action" in blocked.observation, false); assert.deepEqual(turn.next_effect.cli_actions, []); assert.deepEqual(interpretTurnJournal(request()), { ok: true, diff --git a/tests/fixtures/turn_envelope_state_matrix.json b/tests/fixtures/turn_envelope_state_matrix.json index 8e8ca8ed45..01a0b0456e 100644 --- a/tests/fixtures/turn_envelope_state_matrix.json +++ b/tests/fixtures/turn_envelope_state_matrix.json @@ -104,7 +104,7 @@ "patch": { "decision": "skip", "should_run": false, - "effective_action": "operator_gate", + "effective_action": "operator_gate_notify", "state": "operator_gate", "normal_delivery_allowed": false, "action_required": true, @@ -168,7 +168,7 @@ "expected": { "decision": "skip", "should_run": false, - "effective_action": "operator_gate", + "effective_action": "operator_gate_notify", "state": "operator_gate", "action.must_attempt": false, "user.action_required": true, diff --git a/tests/test_loopx_turn_executor.py b/tests/test_loopx_turn_executor.py index 7aa42779b9..7c0f5ea311 100644 --- a/tests/test_loopx_turn_executor.py +++ b/tests/test_loopx_turn_executor.py @@ -1180,6 +1180,20 @@ def test_run_once_commits_once_and_replays_without_duplicate_effects( assert count_path.read_text(encoding="utf-8") == "1" assert calls == {"writeback": 1, "spend": 1, "scheduler": 1} + # The route is a persisted compatibility surface, not just in-process state. + # Exercise the actual TypeScript-backed journal writer and Python resume reader. + transaction = plan["transaction"] + assert isinstance(transaction, dict) + turn_key = str(transaction["turn_key"]) + stored = json.loads(turn_journal_path( + tmp_path / "runtime", goal_id="fixture-goal", turn_key=turn_key, + ).read_text(encoding="utf-8")) + assert stored["plan"]["route"]["kind"] == "ready_for_host" + resumed = load_loopx_turn_plan_from_journal( + tmp_path / "runtime", goal_id="fixture-goal", turn_key=turn_key, + ) + assert resumed["route"] == stored["plan"]["route"] + def test_provider_can_commit_before_its_journal_checkpoint( tmp_path: Path,