Skip to content

Commit aa929ac

Browse files
YZJFYue021130
authored andcommitted
docs(catalog): add IP-032 completed-work archive with durable decision retention
`loopx todo archive-completed` encodes three interaction rules that no catalog entry explains to humans: 1. a done todo carrying a durable standing decision is not a move candidate, and the decision must still resolve as active standing authority after the move (`retained_standing_decision_count`, `standing_decision_authority_v0`); 2. the archive only touches the section for the requested role, so `--role user` must leave `Agent Todo` alone while the role defaults to `agent`; 3. without `--execute` the command is a preview that must not change the state file. Today `examples/control_plane/todo-archive-completed-smoke.py` and `examples/control_plane/todo-standing-decision-authority-smoke.py` encode this behavior but nothing in the catalog names it, which is exactly the "a smoke encodes a behavior that is not yet explained to humans" case in the catalog maintenance rules. The entry explicitly separates itself from IP-020 (claim / supersede / successor lifecycle) and IP-014 (how a decision is written): neither owns what happens to a durable decision once the todo carrying it leaves the active window. Pattern-To-Canary matrix: IP-032 joins State And Boundary, and "completed-work archive" joins that family's trigger surfaces. The catalog smoke also gains a data-driven structural check: pattern ids own exactly one table row, every row has its detail heading (and vice versa), and no id is listed under more than one family in the Pattern-To-Canary matrix, so an id collision like the one this entry initially shipped cannot pass silently again. Rebased onto main f4ed58d; the entry is numbered IP-032 because IP-030 (Machine Configuration Preview And Revision-Guarded Apply) and IP-031 (Manager Context Is Not Turn Authority) are already taken on main. Validation: - python3 examples/interaction-pattern-catalog-smoke.py -> ok (now also enforces id uniqueness / family membership / detail-heading pairing) - python3 examples/canary/catalog-planner-smoke.py -> ok - python3 examples/docs-governance-smoke.py -> ok - loopx check --scan-path docs/concepts/interaction-pattern-catalog.md -> ok, errors=0, public boundary scan clean - mutation check: duplicating an id or listing it under two families now fails the new structural assertions Role isolation is documented as a future CLI-level smoke because it is still helper-level only; the gap is stated rather than hidden. Docs-only; no runtime, validation, or benchmark behavior changes. Signed-off-by: YZJF <195568136+YZJF@users.noreply.github.com>
1 parent 75fcd55 commit aa929ac

2 files changed

Lines changed: 127 additions & 1 deletion

File tree

‎docs/concepts/interaction-pattern-catalog.md‎

Lines changed: 86 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,7 @@ Map P0/P1 catalog rows to canary archetypes before picking commands:
8888
| --- | --- | --- | --- | --- | --- |
8989
| Work Routing | IP-001, IP-002, IP-003, IP-007, IP-008, IP-021, IP-029 | Hot-path route canary; Planning governance canary when cadence or repair is involved | `quota should-run`, `interaction_contract`, `work_lane_contract`, scheduler hint, handoff todo state | one eligible delivery fixture, one blocked/fallback fixture, one quiet or monitor fixture | agent turn routing is unsafe: it may spend, wait, notify, or choose fallback incorrectly |
9090
| Human Decision | IP-004, IP-014, IP-017, IP-027, IP-030 | Scoped decision canary; Product/readiness canary when first-screen human copy changes | user todos, decision scope, operator-gate/reward preview, deferred resume candidates | one concrete user ask, one scoped non-blocking gate, one preview-or-append dry run | humans may be asked the wrong question, or an agent may continue without the needed decision |
91-
| State And Boundary | IP-005, IP-006, IP-011, IP-016, IP-019, IP-020, IP-022, IP-023, IP-025, IP-026, IP-028, IP-031 | Projection and boundary canary; Hot-path route canary when the projection feeds quota/status | active state, todo metadata, task graph, authority source, claim lease, connector runtime policy, public/private scan | fixture state plus structured projection check; boundary scan for touched public files | compact state and executable truth diverge, so dashboards and agents may trust stale or unsafe authority |
91+
| State And Boundary | IP-005, IP-006, IP-011, IP-016, IP-019, IP-020, IP-022, IP-023, IP-025, IP-026, IP-028, IP-031, IP-032 | Projection and boundary canary; Hot-path route canary when the projection feeds quota/status | active state, todo metadata, task graph, authority source, claim lease, completed-work archive, connector runtime policy, public/private scan | fixture state plus structured projection check; boundary scan for touched public files | compact state and executable truth diverge, so dashboards and agents may trust stale or unsafe authority |
9292
| Evidence Lifecycle | IP-012, IP-015 | Evidence lifecycle canary; Product/readiness canary when evidence is rendered | external handle observation, benchmark lifecycle reducer, compact result projection | compact public-safe evidence fixture with raw-material exclusion assertions | progress evidence may be missing, double-counted, or represented with unsafe raw material |
9393
| Planning Governance | IP-010, IP-013, IP-018, IP-024 | Planning governance canary; Hot-path route canary when cadence changes affect execution | stalled run history, autonomous replan obligation, repair delta, cadence hint, plan-to-todo writeback | two-turn stalled fixture plus repair/writeback delta assertion | the agent may keep planning in prose while the machine-visible frontier stays unchanged |
9494

@@ -341,6 +341,7 @@ Projection, authority, write scope, and lease integrity.
341341
| P1 | IP-025 | Experimental Diagnostic Sidecar Boundary | Runtime/protocol owners | no interruption unless an opt-in proof asks for user action | keep proof/debug verdicts as sidecar diagnostics until a product-general schema is validated |
342342
| P1 | IP-028 | Connector Runtime Boundary | Connector/runtime owners | notify only if the required owner decision is missing | enforce runtime allow/deny policy before browser or API connector reads can autoload raw material |
343343
| P1 | IP-031 | Manager Context Is Not Turn Authority | Manager connection owner | no interruption; retention is silent | retain group context only and act only on a provider-native mention, verified reply, or existing typed authority |
344+
| P1 | IP-032 | Completed Work Archive With Durable Decision Retention | Archive selector plus controller | no interruption; preview-then-execute readback | treat archived done work as history, keep durable decisions authoritative, and never move another role's lane |
344345

345346
### Evidence Lifecycle
346347

@@ -2255,6 +2256,90 @@ request database.
22552256
- `tests/extensions/test_lark_goal_topic_runtime.py`;
22562257
- `tests/extensions/test_lark_goal_topic_connections.py`.
22572258

2259+
#### IP-032 Completed Work Archive With Durable Decision Retention
2260+
2261+
**Trigger**
2262+
2263+
- a role-scoped todo lane holds more done todos than the active window allows,
2264+
so `loopx todo archive-completed --max-active-done <n>` has something to move;
2265+
- a done todo carries a durable decision receipt, so later turns still depend on
2266+
it even though the todo itself is finished; and
2267+
- the caller picks a lane with `--role user` or `--role agent`, and omits
2268+
`--execute` for a preview.
2269+
2270+
**Expected behavior**
2271+
2272+
Archive is a storage move, not a decision loss. The command moves finished work
2273+
out of the active lane into the `Completed Work Archive` section, and three
2274+
rules bound what that move may do.
2275+
2276+
1. **Retention.** A done todo carrying a durable standing decision is not a move
2277+
candidate. The payload reports `retained_standing_decision_count`, and after
2278+
the move the decision still resolves as active standing authority under
2279+
`standing_decision_authority_v0`. Archiving completed work must never be the
2280+
reason a settled policy has to be re-decided.
2281+
2. **Role scope.** The archive only touches the section for the requested role.
2282+
`--role user` moves out of `User Todo / Owner Review Reading Queue` and must
2283+
leave `Agent Todo` untouched. The role defaults to `agent`, so a caller that
2284+
means the user lane has to say so. A todo whose role contradicts its active
2285+
section is rejected rather than silently relocated.
2286+
3. **Preview.** Without `--execute` the command is a dry run. The preview must
2287+
not change the state file, and the preview payload must describe exactly what
2288+
the execute run would move.
2289+
2290+
Moved blocks keep their role identity with a `<!-- loopx:todo role=... -->`
2291+
marker inside the mixed archive section, so the archive stays readable by lane
2292+
instead of collapsing ownership into one undifferentiated list.
2293+
2294+
IP-020 owns claim, supersede, and successor lifecycle, and IP-014 owns how a
2295+
decision is written. Neither owns what happens to a durable decision when the
2296+
todo carrying it leaves the active window, which is the gap this pattern fills.
2297+
2298+
**Visual Model**
2299+
2300+
```mermaid
2301+
flowchart TD
2302+
A["done todos exceed --max-active-done"] --> P{"--execute?"}
2303+
P -->|"no"| V["preview payload, state file unchanged"]
2304+
P -->|"yes"| R{"requested --role"}
2305+
R -->|"user"| U["scan User Todo section only"]
2306+
R -->|"agent"| G["scan Agent Todo section only"]
2307+
U --> S{"todo carries durable standing decision?"}
2308+
G --> S
2309+
S -->|"yes"| K["retain in active lane<br/>retained_standing_decision_count += 1"]
2310+
S -->|"no"| M["move to Completed Work Archive<br/>preserve role marker"]
2311+
K --> Z["authority still resolves as active"]
2312+
M --> Z
2313+
```
2314+
2315+
**Bad smell**
2316+
2317+
An agent tidies the active lane, the durable policy decision is compressed away
2318+
with the todo that carried it, and two turns later the agent re-asks a question
2319+
the user already answered or re-litigates an approved policy. The archive
2320+
"cleaned up" the only durable record of the decision.
2321+
2322+
The opposite bad smell is ownership bleed: an operator runs `--role user`
2323+
expecting to tidy the user lane and the agent lane moves too, so the archive
2324+
section mixes decisions nobody can attribute later. A third bad smell is
2325+
treating a dry-run preview as applied, after which status and projection
2326+
quietly disagree with what the operator believes happened.
2327+
2328+
**Validation**
2329+
2330+
- `examples/control_plane/todo-archive-completed-smoke.py` owns the CLI-level
2331+
archive move, preview, and payload metadata.
2332+
- `examples/control_plane/todo-standing-decision-authority-smoke.py` owns
2333+
standing-decision retention and authority, including
2334+
`assert_archive_retains_standing_receipt`.
2335+
- `loopx/control_plane/todos/completed_archive.py` and
2336+
`loopx/control_plane/coordination/todo_archive_selection.ts` own the typed
2337+
selector behind the command.
2338+
- `examples/interaction-pattern-catalog-smoke.py` protects this entry.
2339+
- Future smoke: role isolation is currently proven at helper level; a CLI-level
2340+
assertion that `--role user` leaves `Agent Todo` byte-identical is proposed
2341+
and not yet landed.
2342+
22582343
### Evidence Lifecycle
22592344

22602345
#### IP-012 External Evidence Observation

‎examples/interaction-pattern-catalog-smoke.py‎

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33

44
from __future__ import annotations
55

6+
import re
67
import sys
78
from pathlib import Path
89

@@ -30,6 +31,39 @@ def require(text: str, snippets: list[str], *, source: Path) -> None:
3031
assert not missing, f"{source}: missing {missing}"
3132

3233

34+
def require_catalog_structure(text: str, *, source: Path) -> None:
35+
table_ids = re.findall(r"^\| P\d \| (IP-\d{3}) \| ", text, re.MULTILINE)
36+
duplicated = sorted({pid for pid in table_ids if table_ids.count(pid) > 1})
37+
assert not duplicated, f"{source}: pattern ids own more than one table row: {duplicated}"
38+
39+
detail_ids = re.findall(r"^#### (IP-\d{3}) ", text, re.MULTILINE)
40+
missing_detail = sorted(set(table_ids) - set(detail_ids))
41+
assert not missing_detail, f"{source}: pattern rows without a detail heading: {missing_detail}"
42+
orphan_detail = sorted(set(detail_ids) - set(table_ids))
43+
assert not orphan_detail, f"{source}: detail headings without a pattern row: {orphan_detail}"
44+
45+
matrix_block = re.search(
46+
r"^\| Family \| P0/P1 Pattern Coverage \|[^\n]*\n\|[^\n]*\|\n(.*?)\n\n",
47+
text,
48+
re.MULTILINE | re.DOTALL,
49+
)
50+
assert matrix_block, f"{source}: Pattern-To-Canary matrix block not found"
51+
family_cells = re.findall(
52+
r"^\| [^|]+ \| ((?:IP-\d{3}, )*IP-\d{3}) \|",
53+
matrix_block.group(1),
54+
re.MULTILINE,
55+
)
56+
family_ids = [pid for cell in family_cells for pid in cell.split(", ")]
57+
split_families = sorted({pid for pid in family_ids if family_ids.count(pid) > 1})
58+
assert not split_families, (
59+
f"{source}: pattern ids listed under more than one family: {split_families}"
60+
)
61+
unknown_matrix_ids = sorted(set(family_ids) - set(table_ids))
62+
assert not unknown_matrix_ids, (
63+
f"{source}: family matrix lists ids without a pattern row: {unknown_matrix_ids}"
64+
)
65+
66+
3367
def main() -> int:
3468
catalog = CATALOG.read_text(encoding="utf-8")
3569
state_model = STATE_MODEL.read_text(encoding="utf-8")
@@ -104,6 +138,11 @@ def main() -> int:
104138
"browser_open_allowed_before_gate: false",
105139
"message-list or\nmessage-detail APIs",
106140
"UI display limit must not become the control-plane reasoning window",
141+
"IP-032 | Completed Work Archive With Durable Decision Retention",
142+
"Archive is a storage move, not a decision loss.",
143+
"retained_standing_decision_count",
144+
"The role defaults to `agent`",
145+
"examples/control_plane/todo-archive-completed-smoke.py",
107146
"## Catalog Maintenance And Validation Design",
108147
"Do not add\na new IP merely because a maintainer needs a validation technique",
109148
"Those are uses of the\ncatalog, not catalog patterns by themselves.",
@@ -164,6 +203,8 @@ def main() -> int:
164203
source=SELF_REPAIR_PATTERNS,
165204
)
166205

206+
require_catalog_structure(catalog, source=CATALOG)
207+
167208
# Every registered built-in machine-configuration namespace must be
168209
# discoverable from the catalog, so a new capability cannot land as a
169210
# silent omission in the IP-030 inventory.

0 commit comments

Comments
 (0)