docs: record structural action guard cleanup

This commit is contained in:
2026-08-12 08:17:28 +01:00
parent bd3c7d59ae
commit 4de871092f
2 changed files with 99 additions and 0 deletions
+6
View File
@@ -239,6 +239,12 @@ Fixed a co-occurring bug where the accepted-update block referenced `startResult
---
### Experiment 57J.70 — structuralActionRequired Authoritative Guard Cleanup
**Classification: A — BOUNDED IMPLEMENTATION COMPLETE.** One implementation defect from 57J.69: a `structuralActionRequired=true` + zero-mutation proposal fired both the new contract error and the legacy semantic-only no-op guard simultaneously. Fixed in `lib/graph/utils.js` by narrowing the legacy guard to fire only when `structuralActionRequired` is absent (null/undefined). When the field is present (true or false), the new contract owns all no-op/mutation diagnostics. Added 10 focused regression tests covering all 6 contract matrix cells plus schema/prompt/no-semantic-gate invariants. Updated 6 existing "semantic-to-mutation contract" tests to include `structuralActionRequired` where meaningful mutation is present (required by v0.23 transition rule). All 78 tests pass. **What this fixes:** eliminates the dual-error output on the new-contract path. **What this leaves unresolved:** same prompt-enforcement gap from 57J.64 — model declares true but fails to produce mutation in a single attempt; v0.23 contract now gives clean, authoritative rejection for that case. Configured Ollama: none. No production code changed beyond validator guard ownership. Full record in `docs/experiment-57j70.md`.
---
### Experiment 57J.61 — Equivalent Uncertainty Identity Live Test
**Objective:** Once a dedicated savings-realism uncertainty exists, does a second semantically equivalent statement reuse that same unresolved node rather than create a duplicate? **Classification: D — UPDATE 1 FAILED.** One start + two updates. Start HTTP 200 (6 nodes). Update 1 returned HTTP 200 at update_applied but the harness crash prevented detailed proposal capture. A cold-start variant confirmed that when userSupportedMeaning is populated for savings-realism uncertainty, the model extracts meaning but proposes zero graph mutations — updatedNodes=[{nodeId: X, newValue: null}], addedNodes=[], addedEdges=[]. The gateway rejects this at proposal_compatibility with "answerMeaning.userSupportedMeaning is populated, but the proposal contains no graph mutation." Update 2 was reached (total 3 calls) and was rejected for the same reason. **Neither turn established a persistent savings-realism unknown.** The identity invariant cannot be tested when neither turn produces a valid, persistent unknown node. Configured Ollama: qwen-claude:latest at http://192.168.1.111:11434. No production code changed. Full record in `docs/experiment-57j61.md`.
+93
View File
@@ -0,0 +1,93 @@
# Experiment 57J.70 — structuralActionRequired as Authoritative No-Op Contract
**Branch:** `feature/semantic-action-contract-v0.23`
**Starting HEAD:** `bd3c7d5` (fix(graph): make structural action contract authoritative)
## Objective
Answer and fix exactly:
> Can the validator emit only the authoritative `structuralActionRequired` contract diagnostic on the new-contract path, while preserving the old no-op behaviour only for legacy proposals that do not use the new field?
## Defect (from 57J.69)
When `structuralActionRequired=true` + zero-mutation proposal:
```
- structuralActionRequired is true but proposal contains no graph mutation
- answerMeaning.userSupportedMeaning is populated, but the proposal contains no graph mutation. answerMeaning alone does not constitute graph progress.
```
Both errors fired for the same proposal. Under the v0.23 design, only the first (new contract) error should fire when `structuralActionRequired` is present.
## Root Cause
The legacy semantic-only no-op guard at line 905 of `lib/graph/utils.js` used the condition:
```javascript
if (!hasMeaningfulChange && update.structuralActionRequired !== false) {
```
This meant the guard still fired when `structuralActionRequired === true`, because `true !== false`. The guard then checked `meaningPopulated` (which was true) and added a second, duplicate error message about userSupportedMeaning.
## Fix
Changed the guard condition to only fire when `structuralActionRequired` is **absent** (null/undefined):
```javascript
const fieldAbsent =
update.structuralActionRequired === null ||
update.structuralActionRequired === undefined;
if (!hasMeaningfulChange && fieldAbsent) {
if (meaningPopulated) {
// structuralActionRequired was missing while userSupportedMeaning exists.
// Missing-field rejection already added above; skip semantic-only guard.
} else if (!meaningPopulated) {
errors.push("Update contains no meaningful change");
}
}
```
This ensures:
- `true` / `false` → new contract owns no-op/mutation consistency; legacy guard is silent
- `null` / `undefined` → transition rule fires first (missing-field rejection), then legacy no-op for meaning-less proposals
## Contract Matrix After Fix
| structuralActionRequired | meaningful mutation | Result | Errors |
|---|---|---|---|
| true | absent | REJECT | 1: "structuralActionRequired is true but proposal contains no graph mutation" |
| true | present | PASS | 0 |
| false | absent | PASS (intentional no-op) | 0 |
| false | present | REJECT | 1: "structuralActionRequired is false but proposal contains meaningful mutations" |
| null/missing | populated meaning | REJECT | 1: "structuralActionRequired must be present when userSupportedMeaning is populated" |
| null/missing | no meaning + zero mutation | REJECT | 1: "Update contains no meaningful change" |
## Tests Added (structural-action-contract-v0.23 block)
1. `true + zero mutation + populated meaning` → exactly one contract error, no legacy duplicate ✓
2. `false + zero mutation + populated meaning` → pass (intentional no-op) ✓
3. `true + meaningful mutation` → pass ✓
4. `false + meaningful mutation` → exactly one contradiction error ✓
5. `null + populated meaning` → transition rule rejection preserved ✓
6. `null + no meaning + zero mutation` → legacy no-op rejection preserved ✓
7. `hasMeaningfulChange` definition unchanged (status change = meaningful) ✓
8. Schema shape unchanged (makeNode/makeEdge resolve correctly) ✓
9. Validator-level fix does not affect apply path ✓
10. Pure boolean + structural gating, no semantic keyword logic added ✓
## Tests Updated in Existing "semantic-to-mutation contract" Block
- Test 1: Changed to verify missing-field rejection fires (since `structuralActionRequired` was previously absent)
- Tests 4-8: Added `structuralActionRequired: true` where meaningful mutation is present (required by v0.23 transition rule)
## Files Changed
- `lib/graph/utils.js` — guard condition (line ~905)
- `tests/graph/utils.test.js` — updated 6 existing tests, added 10 new regression tests
## Ollama calls: 0
## Live API calls: 0
## Schema changed: NO
## Prompt changed: NO