@junghanacs/entwurf 0.15.0 → 0.15.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,92 @@
1
+ {
2
+ "schemaVersion": 1,
3
+ "lane": "pi-package-ownership",
4
+ "mutants": [
5
+ {
6
+ "claim": "SILENT-TAKEOVER",
7
+ "title": "a normal user-scope install silently replaces another LIVE root's registration, resurrecting the last-writer-wins takeover the #86 C2 ownership contract retires (only the operator-explicit takeover-user-scope may move the shared entry)",
8
+ "subject": "scripts/register-pi-package.py",
9
+ "find": [
10
+ " if os.path.isdir(owner):\n _refuse(6, \"register-pi-package: the user-scope entwurf registration is owned by another LIVE root: \"\n f\"{owner}. Normal install/setup never replaces another owner — zero settings bytes written. \"\n f\"Run './run.sh takeover-user-scope' from {repo_dir} to explicitly move the shared entry.\")"
11
+ ],
12
+ "replace": [
13
+ " if os.path.isdir(owner):\n return register_user(settings_path, repo_dir_arg, state_path, True)"
14
+ ],
15
+ "gate": ["bash", "run.sh", "smoke-user-scope-citizen"],
16
+ "timeoutSeconds": 300,
17
+ "signature": "[QK:SILENT-TAKEOVER]",
18
+ "signatureSource": "scripts/smoke-user-scope-citizen.sh"
19
+ },
20
+ {
21
+ "claim": "FOREIGN-INVERSE-REMOVE",
22
+ "title": "an old root's inverse removes the CURRENT live owner's user-scope registration, so one stale checkout's remove-user-scope deletes the shared entry every other project depends on",
23
+ "subject": "scripts/register-pi-package.py",
24
+ "find": [
25
+ " if os.path.isdir(owner):\n _refuse(6, \"register-pi-package: the user-scope registration is owned by another LIVE root: \"\n f\"{owner}. This root's inverse must not remove someone else's registration — zero settings \"\n \"bytes written. Run remove from that root, or take the entry over first.\")"
26
+ ],
27
+ "replace": [
28
+ " if os.path.isdir(owner):\n n, kept = remove(settings_path, owner)\n state_path.unlink()\n print(f\"remove: removed {n} entries (foreign owner ignored)\")\n return 0"
29
+ ],
30
+ "gate": ["bash", "run.sh", "smoke-user-scope-citizen"],
31
+ "timeoutSeconds": 300,
32
+ "signature": "[QK:FOREIGN-INVERSE-REMOVE]",
33
+ "signatureSource": "scripts/smoke-user-scope-citizen.sh"
34
+ },
35
+ {
36
+ "claim": "BROAD-INSTALL-COLLAPSE",
37
+ "title": "a same-owner user-scope install routes through the broad project-scope register() collapse, so a second entwurf-shaped root in the GLOBAL packages[] is silently deleted as collateral of an ordinary install — the exact transition B reproduced against 096a5cd (#86 C2 corrective amendment, blocker 1)",
38
+ "subject": "scripts/register-pi-package.py",
39
+ "find": [
40
+ " _require_user_owner_entry(settings_path, owner)\n if preflight:\n print(\"preflight: install ok (this root owns one unambiguous registration)\")\n return 0\n print(f\"install: entwurf package already registered (no-op) -> {repo_dir}\")\n return 0"
41
+ ],
42
+ "replace": [
43
+ " if preflight:\n print(\"preflight: install ok (this root owns one unambiguous registration)\")\n return 0\n register(settings_path, repo_dir_arg)\n print(f\"install: entwurf package already registered (no-op) -> {repo_dir}\")\n return 0"
44
+ ],
45
+ "gate": ["bash", "run.sh", "smoke-user-scope-citizen"],
46
+ "timeoutSeconds": 300,
47
+ "signature": "[QK:BROAD-INSTALL-COLLAPSE]",
48
+ "signatureSource": "scripts/smoke-user-scope-citizen.sh"
49
+ },
50
+ {
51
+ "claim": "BROAD-DOCTOR-GREEN",
52
+ "title": "doctor-pi-package judges the recorded owner through the broad entwurf-shape matcher, so a store whose exact owner entry is GONE but which still holds some other entwurf-shaped root reads owned/exit-0 while the inverse refuses the very same state — contradictory verdicts on one store (#86 C2 corrective amendment, blocker 2)",
53
+ "subject": "scripts/register-pi-package.py",
54
+ "find": [
55
+ " owner_exact, owner_extra = _classify_user_owner_entries(settings_path, owner)\n if len(owner_exact) != 1 or owner_extra:\n print(f\"doctor-pi-package: mismatch — owner state records {owner} but the settings hold \"\n f\"{len(owner_exact)} exact entr{'y' if len(owner_exact) == 1 else 'ies'} and \"\n f\"{len(owner_extra)} additional entwurf-shaped entr{'y' if len(owner_extra) == 1 else 'ies'} \"\n \"(expected exactly one exact entry and no other entwurf-shaped root)\")\n return 1"
56
+ ],
57
+ "replace": [
58
+ " owner_matches = _entwurf_matches(packages, owner, settings_dir)\n if not owner_matches:\n print(f\"doctor-pi-package: mismatch — owner state records {owner} but no packages[] entry names it\")\n return 1"
59
+ ],
60
+ "gate": ["bash", "run.sh", "smoke-user-scope-citizen"],
61
+ "timeoutSeconds": 300,
62
+ "signature": "[QK:BROAD-DOCTOR-GREEN]",
63
+ "signatureSource": "scripts/smoke-user-scope-citizen.sh"
64
+ },
65
+ {
66
+ "claim": "PROVIDER-ROOT-TYPE-FAIL-OPEN",
67
+ "title": "the typed installerRoot classifier reports a wrong-TYPE value (number, empty string, bool, object, array) as LEGACY instead of CORRUPT, so provider install adopts and rebinds an unattributed state, remove proceeds on it, and the doctor coupling stays silent — the fall-through B reproduced against 096a5cd (#86 C2 corrective amendment, blocker 3)",
68
+ "subject": "scripts/pi_settings_io.py",
69
+ "find": [" if isinstance(root, str) and root:\n return \"owner\", root\n return \"corrupt\", None"],
70
+ "replace": [
71
+ " if isinstance(root, str) and root:\n return \"owner\", root\n return \"legacy\", None"
72
+ ],
73
+ "gate": ["bash", "run.sh", "smoke-user-scope-citizen"],
74
+ "timeoutSeconds": 300,
75
+ "signature": "[QK:PROVIDER-ROOT-TYPE-FAIL-OPEN]",
76
+ "signatureSource": "scripts/smoke-user-scope-citizen.sh"
77
+ },
78
+ {
79
+ "claim": "MISSING-OWNER-AUTO-TAKEOVER",
80
+ "title": "a normal install auto-claims a MISSING owner's registration, turning every moved or deleted checkout into a silent ownership transfer instead of the named missing-owner verdict plus explicit takeover/orphan path",
81
+ "subject": "scripts/register-pi-package.py",
82
+ "find": [
83
+ " _refuse(6, \"register-pi-package: the user-scope entwurf registration is owned by a MISSING root: \"\n f\"{owner} (doctor verdict: missing-owner). Normal install/setup still refuses — zero settings \"\n \"bytes written. Use './run.sh takeover-user-scope' to claim it explicitly, or \"\n \"'./run.sh remove-user-scope' for the aligned orphan cleanup.\")"
84
+ ],
85
+ "replace": [" return register_user(settings_path, repo_dir_arg, state_path, True)"],
86
+ "gate": ["bash", "run.sh", "smoke-user-scope-citizen"],
87
+ "timeoutSeconds": 300,
88
+ "signature": "[QK:MISSING-OWNER-AUTO-TAKEOVER]",
89
+ "signatureSource": "scripts/smoke-user-scope-citizen.sh"
90
+ }
91
+ ]
92
+ }
@@ -0,0 +1,148 @@
1
+ {
2
+ "schemaVersion": 1,
3
+ "lane": "setup-verdict",
4
+ "mutants": [
5
+ {
6
+ "claim": "SETUP-FALSE-GREEN",
7
+ "title": "aggregate setup prints its computed NON-GREEN summary but exits 0 again, resurrecting the retired whole-host false success in the only surface an operator reads",
8
+ "subject": "run.sh",
9
+ "find": [
10
+ " echo \"DONE: entwurf setup — result: NON-GREEN (FAIL:$s_fails). Valid components above remain installed; repair the named ones and re-run setup.\"\n return 1"
11
+ ],
12
+ "replace": [
13
+ " echo \"DONE: entwurf setup — result: NON-GREEN (FAIL:$s_fails). Valid components above remain installed; repair the named ones and re-run setup.\"\n return 0"
14
+ ],
15
+ "gate": ["bash", "scripts/check-setup-qualification.sh"],
16
+ "timeoutSeconds": 120,
17
+ "signature": "[QK:SETUP-FALSE-GREEN]",
18
+ "signatureSource": "scripts/check-setup-qualification.sh"
19
+ },
20
+ {
21
+ "claim": "SETUP-PI-FLOOR-SKIP",
22
+ "title": "a resolvable pi below the supported floor is relabeled zero-state SKIP, so a stale-harness host reads green instead of being told its detected integration is incomplete",
23
+ "subject": "run.sh",
24
+ "find": [
25
+ " setup_result pi FAIL \"detected pi ${pi_ver:-<unreadable version>} is outside the supported range $pi_range — Pi wiring not written; align pi, then re-run setup\""
26
+ ],
27
+ "replace": [
28
+ " setup_result pi SKIP \"detected pi ${pi_ver:-<unreadable version>} is outside the supported range $pi_range — Pi wiring not written; align pi, then re-run setup\""
29
+ ],
30
+ "gate": ["bash", "scripts/check-setup-qualification.sh"],
31
+ "timeoutSeconds": 120,
32
+ "signature": "[QK:SETUP-PI-FLOOR-SKIP]",
33
+ "signatureSource": "scripts/check-setup-qualification.sh"
34
+ },
35
+ {
36
+ "claim": "SETUP-INSTALLED-NO-BOOTSTRAP",
37
+ "title": "installed-package setup re-enters the source pnpm bootstrap inside node_modules, so the mode-first contract collapses and a neutral npm consumer's setup dies or mutates the installed tree",
38
+ "subject": "run.sh",
39
+ "find": [
40
+ " if [ \"$mode\" = \"source\" ]; then\n # Source bootstrap is the ONLY install-mode difference (#86): the pinned"
41
+ ],
42
+ "replace": [
43
+ " if [ -n \"$mode\" ]; then\n # Source bootstrap is the ONLY install-mode difference (#86): the pinned"
44
+ ],
45
+ "gate": ["bash", "scripts/check-setup-qualification.sh"],
46
+ "timeoutSeconds": 120,
47
+ "signature": "[QK:SETUP-INSTALLED-NO-BOOTSTRAP]",
48
+ "signatureSource": "scripts/check-setup-qualification.sh"
49
+ },
50
+ {
51
+ "claim": "SETUP-CREDENTIAL-FREE",
52
+ "title": "a credential-store write (auth.json backup) resurrects inside aggregate setup, re-violating the corrected installation rule that setup never reads, copies, aliases or backs up harness credentials",
53
+ "subject": "run.sh",
54
+ "find": [" SETUP_RESULTS=()\n\n if [ \"$mode\" = \"source\" ]; then"],
55
+ "replace": [
56
+ " SETUP_RESULTS=()\n local _auth=\"${PI_CODING_AGENT_DIR:-$HOME/.pi/agent}/auth.json\"\n if [ -f \"$_auth\" ]; then cp \"$_auth\" \"$_auth.bak\"; fi\n\n if [ \"$mode\" = \"source\" ]; then"
57
+ ],
58
+ "gate": ["bash", "scripts/check-setup-qualification.sh"],
59
+ "timeoutSeconds": 120,
60
+ "signature": "[QK:SETUP-CREDENTIAL-FREE]",
61
+ "signatureSource": "scripts/check-setup-qualification.sh"
62
+ },
63
+ {
64
+ "claim": "DEV-BIN-ATTEMPTS-ALL",
65
+ "title": "the no-arg dev-bin expose loop aborts at the first foreign refusal again, so units after it are never attempted while the caller's summary implies they ran",
66
+ "subject": "scripts/dev-bin.sh",
67
+ "find": [
68
+ " rc=0; expose_one \"$b\" || rc=$?\n if [ \"$rc\" -eq 0 ]; then exposed=$((exposed + 1)); else refused=\"$refused $b\"; fi"
69
+ ],
70
+ "replace": [
71
+ " rc=0; expose_one \"$b\" || rc=$?\n if [ \"$rc\" -eq 0 ]; then exposed=$((exposed + 1)); else warn \"[dev-bin] REFUSE aborts the loop\"; exit 3; fi"
72
+ ],
73
+ "gate": ["bash", "run.sh", "smoke-agy-install-state"],
74
+ "timeoutSeconds": 600,
75
+ "signature": "[QK:DEV-BIN-ATTEMPTS-ALL]",
76
+ "signatureSource": "scripts/smoke-agy-install-state.sh"
77
+ },
78
+ {
79
+ "claim": "SETUP-INSTALLED-NO-PNPM",
80
+ "title": "installed setup regains an unconditional pnpm requirement, so a neutral npm consumer without pnpm is refused by an accidental Missing-command instead of composing — the mode decision stops being independent of the source toolchain",
81
+ "subject": "run.sh",
82
+ "find": [
83
+ " if [ \"$mode\" = \"source\" ]; then\n # Source-only dev fixtures: the pinned repo dependencies are build/test\n # fixtures, never a product promise that consumers receive them (#86 A4).\n require_cmd pnpm\n fi"
84
+ ],
85
+ "replace": [" require_cmd pnpm"],
86
+ "gate": ["bash", "scripts/check-setup-qualification.sh"],
87
+ "timeoutSeconds": 120,
88
+ "signature": "[QK:SETUP-INSTALLED-NO-PNPM]",
89
+ "signatureSource": "scripts/check-setup-qualification.sh"
90
+ },
91
+ {
92
+ "claim": "SETUP-COPILOT-ABSENT-SKIP",
93
+ "title": "the copilot presence gate collapses to always-detected, so an absent-copilot host loses its zero-state SKIP and setup composes (and writes) Copilot units nobody asked for",
94
+ "subject": "run.sh",
95
+ "find": [
96
+ " if ! command -v \"${COPILOT_BIN:-copilot}\" >/dev/null 2>&1; then\n setup_result copilot SKIP \"copilot not on PATH — zero Copilot wiring written\""
97
+ ],
98
+ "replace": [
99
+ " if false; then\n setup_result copilot SKIP \"copilot not on PATH — zero Copilot wiring written\""
100
+ ],
101
+ "gate": ["bash", "scripts/check-setup-qualification.sh"],
102
+ "timeoutSeconds": 120,
103
+ "signature": "[QK:SETUP-COPILOT-ABSENT-SKIP]",
104
+ "signatureSource": "scripts/check-setup-qualification.sh"
105
+ },
106
+ {
107
+ "claim": "SETUP-COPILOT-INDEPENDENT",
108
+ "title": "a failed Copilot birth aborts the whole composition, so the MCP/receiver/footer units are silently never attempted while the operator reads only the first failure",
109
+ "subject": "run.sh",
110
+ "find": [
111
+ " setup_result copilot-birth FAIL \"detected copilot, but the birth plugin install did not complete (see above) — repair, then re-run setup\"\n fi"
112
+ ],
113
+ "replace": [
114
+ " setup_result copilot-birth FAIL \"detected copilot, but the birth plugin install did not complete (see above) — repair, then re-run setup\"\n return 1\n fi"
115
+ ],
116
+ "gate": ["bash", "scripts/check-setup-qualification.sh"],
117
+ "timeoutSeconds": 120,
118
+ "signature": "[QK:SETUP-COPILOT-INDEPENDENT]",
119
+ "signatureSource": "scripts/check-setup-qualification.sh"
120
+ },
121
+ {
122
+ "claim": "SETUP-COPILOT-COSMETIC-PASS",
123
+ "title": "the birth unit's failure capture is swallowed, so a refused vendor preflight reads as copilot-birth PASS and a broken detected integration reports a green composition",
124
+ "subject": "run.sh",
125
+ "find": [" copilot_rc=0; (cd \"$REPO_DIR\" && bash scripts/copilot-bridge-install.sh) || copilot_rc=$?"],
126
+ "replace": [" copilot_rc=0; (cd \"$REPO_DIR\" && bash scripts/copilot-bridge-install.sh) || true"],
127
+ "gate": ["bash", "scripts/check-setup-qualification.sh"],
128
+ "timeoutSeconds": 120,
129
+ "signature": "[QK:SETUP-COPILOT-COSMETIC-PASS]",
130
+ "signatureSource": "scripts/check-setup-qualification.sh"
131
+ },
132
+ {
133
+ "claim": "SETUP-AGY-COSMETIC-RETURN",
134
+ "title": "wire_agy_bridge's failed-install path returns 0 again, restoring the WARN-then-success mechanism that let a detected-but-refused agy integration read as a green setup",
135
+ "subject": "run.sh",
136
+ "find": [
137
+ " echo \"[setup] Bridge NOT wired; verify with ./run.sh doctor-agy-bridge. setup continues.\" >&2\n ;;\n esac\n return 1\n}"
138
+ ],
139
+ "replace": [
140
+ " echo \"[setup] Bridge NOT wired; verify with ./run.sh doctor-agy-bridge. setup continues.\" >&2\n ;;\n esac\n return 0\n}"
141
+ ],
142
+ "gate": ["bash", "run.sh", "smoke-agy-install-state"],
143
+ "timeoutSeconds": 600,
144
+ "signature": "[QK:SETUP-AGY-COSMETIC-RETURN]",
145
+ "signatureSource": "scripts/smoke-agy-install-state.sh"
146
+ }
147
+ ]
148
+ }
@@ -0,0 +1,32 @@
1
+ {
2
+ "schemaVersion": 1,
3
+ "lane": "source-install",
4
+ "mutants": [
5
+ {
6
+ "claim": "SOURCE-OPERATOR-BIN",
7
+ "title": "source setup again omits the entwurf operator command, so Copilot fresh rejects runtime-unresolved unless an unrelated global package happens to mask the defect",
8
+ "subject": "scripts/dev-bin.sh",
9
+ "find": [
10
+ "MANAGED_BINS=\"entwurf entwurf-bridge entwurf-agy-statusline entwurf-agy-imprint entwurf-copilot-statusline\""
11
+ ],
12
+ "replace": [
13
+ "MANAGED_BINS=\"entwurf-bridge entwurf-agy-statusline entwurf-agy-imprint entwurf-copilot-statusline\""
14
+ ],
15
+ "gate": ["bash", "run.sh", "smoke-agy-install-state"],
16
+ "timeoutSeconds": 300,
17
+ "signature": "[QK:SOURCE-OPERATOR-BIN]",
18
+ "signatureSource": "scripts/smoke-agy-install-state.sh"
19
+ },
20
+ {
21
+ "claim": "SOURCE-OPERATOR-FAILS-LOUD",
22
+ "title": "source setup reports green after the managed operator command is foreign, shadowed, or outside PATH, recreating the runtime-unresolved incident behind a cosmetic success",
23
+ "subject": "run.sh",
24
+ "find": [" if ! bash \"$REPO_DIR/scripts/dev-bin.sh\" verify entwurf; then\n return 1\n fi"],
25
+ "replace": [" if ! bash \"$REPO_DIR/scripts/dev-bin.sh\" verify entwurf; then\n return 0\n fi"],
26
+ "gate": ["bash", "run.sh", "smoke-agy-install-state"],
27
+ "timeoutSeconds": 300,
28
+ "signature": "[QK:SOURCE-OPERATOR-FAILS-LOUD]",
29
+ "signatureSource": "scripts/smoke-agy-install-state.sh"
30
+ }
31
+ ]
32
+ }
@@ -63,3 +63,25 @@ def unchanged(before: dict, after: dict) -> bool:
63
63
  writers mutate in place, so they parse the raw text twice rather than aliasing it.
64
64
  """
65
65
  return before == after
66
+
67
+
68
+ def classify_installer_root(state: dict) -> "tuple[str, str | None]":
69
+ """ONE typed verdict for a user-scope install-state's `installerRoot` (#86 C2).
70
+
71
+ Shared by register-pi-provider install/remove and the doctor-pi-package ownership
72
+ coupling, because two per-site checks (`isinstance(str)` in one arm, `is None` in
73
+ another) let every OTHER type — a number, an empty string, a bool, an object, an
74
+ array — fall between them and proceed unattributed. A state MORE unattributed than
75
+ legacy must never be treated MORE leniently than legacy.
76
+
77
+ ("legacy", None) key absent, or an explicit null — the pre-#86 shape; each
78
+ caller names its own adoption/refusal contract for it.
79
+ ("owner", root) a non-empty string — the recorded owner root.
80
+ ("corrupt", None) everything else — fail-closed before any write.
81
+ """
82
+ if "installerRoot" not in state or state["installerRoot"] is None:
83
+ return "legacy", None
84
+ root = state["installerRoot"]
85
+ if isinstance(root, str) and root:
86
+ return "owner", root
87
+ return "corrupt", None