Feature/product platform foundation v0.62 #1

Merged
robbond merged 683 commits from feature/product-platform-foundation-v0.62 into feature/emergent-unknowns-v0.5 2026-09-09 07:58:20 +01:00
2 changed files with 298 additions and 1 deletions
Showing only changes of commit 9425e7b2d6 - Show all commits
+36 -1
View File
@@ -1151,4 +1151,39 @@ Configured Ollama: none used. **Production code changed:** NO.
- MODEL: supply one valid candidate when new unresolved unknowns are added
- ENGINE: validate candidate, retain deterministic priority/scoring ownership
Configured Ollama: none used. **Production code changed:** prompt + tests only. **Ollama calls:** 0.
Configured Ollama: none used. **Production code changed:** prompt + tests only. **Ollama calls:** 0.
### Experiment 57J.65 — Smallest Enforceable Semantic-to-Mutation Contract (Read-Only Deterministic)
**Objective:** What is the smallest structured contract that lets the model declare whether graph action is required, and lets deterministic code verify that the actual proposal fulfils that declaration? **Classification: B — BOOLEAN STRUCTURAL-ACTION CONTRACT.**
**Part 1 — Existing fields:** C — NEW ACTION DECLARATION REQUIRED. The existing fields encode *what changed* but not *what was intended*. When all mutation arrays are empty, there is no field saying "I intentionally declare zero graph action." The validator's current check (populated userSupportedMeaning + empty mutations → REJECT) treats model silence as error rather than accepting intentional no-op.
**Part 2 — Minimum distinction:** `MUTATION REQUIRED` vs `NO MUTATION REQUIRED`. Reuse/refine existing, add new unknown, resolve existing, other structural mutation are all DERIVABLE FROM PROPOSAL SHAPE (validator checks each independently). Only "no structural change" MUST BE DECLARED because empty arrays alone cannot distinguish intent from omission.
**Part 3 — Three designs evaluated:**
- **Option A (boolean):** Prevents no-op = PARTIAL, checks mutation = YES, schema concept = BOOLEAN, validator complexity = LOW, model-compliance risk = MEDIUM
- **Option B (enum):** Prevents no-op = PARTIAL, checks mutation = YES, schema concept = SMALL ENUM, validator complexity = MEDIUM, model-compliance risk = MEDIUM-HIGH
- **Option C (existing fields only):** Prevents no-op = PARTIAL, but accepts every noncompliant zero-mutation output silently — HIGH model-compliance risk
**Part 4 — No-change case:** YES with new structured declaration. Model sets `structuralActionRequired = false` + empty mutations → validation PASSES by checking actual empty state. Model sets `true` + empty → REJECTS (contradiction). Without the field, intent is unverifiable.
**Part 5 — Relationship to supportCategory:** INDEPENDENT. `supportCategory = "uncertain"` does NOT mean `add new unknown`. Same category can map to different actions depending on graph state (v0.21 identity rule: reuse existing equivalent).
**Part 6 — Deterministic invariants:**
1. `structuralActionRequired = true` + empty mutations → REJECT
2. `structuralActionRequired = false` + meaningful mutation → ACCEPT with diagnostic note
3. Missing field + populated userSupportedMeaning → REJECT
4. `false` + empty mutations → PASS (valid "semantic agreement, no structural change")
**Part 7 — 57J.63 walkthrough:**
- Case A (successful): declares `true`, addedNodes non-empty → PASS by shape comparison
- Case B (no-op): declares `false`, empty arrays → PASS by explicit declaration + confirmed zero mutation
This is ACTUAL CONTRACT ENFORCEMENT because the validator compares a declared boolean against actual proposal shape — no semantic parsing needed. Noncompliant zero-mutation outputs cannot hide behind empty arrays (they must also declare `true`, which fails validation). Intentional no-ops are valid when equivalent structure exists.
**Part 8 — Recommendation: B (boolean structural-action contract).** One new nullable boolean field in `answerMeaningSchema`. One validator invariant check during transition: missing field + populated userSupportedMeaning rejected. After transition, the field is the enforcement mechanism.
**Transition policy: B — missing new field + populated userSupportedMeaning is rejected.** Accepting zero-mutation without the field would make every noncompliant output valid again.
**Convergence:** Ready for bounded implementation. One boolean field + one invariant check. No new semantic taxonomies, no keyword/synonym logic, provider-agnostic.
+262
View File
@@ -0,0 +1,262 @@
# Experiment 57J.65 — Smallest Enforceable Semantic-to-Mutation Contract
**Branch:** `feature/selected-question-contract-v0.22`
**Starting HEAD:** `d7cb343` (experiment: diagnose semantic-to-mutation action ownership)
## Objective
Answer one question:
> What is the smallest structured contract that lets the model declare whether graph action is required, and lets deterministic code verify that the actual proposal fulfils that declaration?
57J.64 established that further prompt-only wording is not the next boundary. This experiment answers with data-contract analysis only.
---
## Part 1 — Are Existing Fields Enough?
**Classification: C — NEW ACTION DECLARATION REQUIRED**
The existing fields provide these capabilities:
| Field | What it expresses |
|-------|-------------------|
| `userSupportedMeaning` | Semantic content (text) of what the user supports |
| `supportCategory` | Category label for semantic content |
| `resolutionGuidance` | Resolution instruction |
| `updatedNodes` | Nodes modified |
| `resolvedUnknownNodeIds` | Unknowns resolved |
| `addedNodes` | New nodes created |
| `addedEdges` | New edges created |
| `selectedQuestion` | Follow-up question candidate |
**Why they are insufficient:**
These fields encode *what changed* but not *what was intended*. When a model intends "I agree with the semantic content, no structural change is needed," it returns empty mutation arrays. There is no explicit field saying "I intentionally declare zero graph action." The validator's current check (line 886 of utils.js) derives intent from:
```
userSupportedMeaning populated + all mutation arrays empty → REJECT
```
This treats the model's silence as an error rather than accepting a valid intentional no-op declaration. It cannot distinguish between "model forgot to mutate" and "model intentionally chose no mutation."
---
## Part 2 — Minimum Required Distinction
**What deterministic validation actually needs:**
The validator does not need to know *why* the model made its choice. It only needs to verify that the model's declared intent matches the proposal shape.
| Intended Action | How validator checks | Classification |
|-----------------|----------------------|----------------|
| Reuse/refine existing | `updatedNodes` references existing node with status/value change | DERIVABLE FROM PROPOSAL SHAPE |
| Add new unknown | `addedUnknownCount > 0` | DERIVABLE FROM PROPOSAL SHAPE |
| Resolve existing | `resolvedUnknownNodeIds.length > 0` | DERIVABLE FROM PROPOSAL SHAPE |
| Other structural mutation | Any non-empty mutation array or addedEdges | DERIVABLE FROM PROPOSAL SHAPE |
| No structural change | All mutation arrays empty | MUST BE DECLARED (by the model) |
**Conclusion: The minimum distinction is `MUTATION REQUIRED` vs `NO MUTATION REQUIRED`.**
Deterministic validation does not need to know *which* mutation type was intended because it checks the actual proposal shape for each possible mutation independently. The only gap is: when all arrays are empty, how do we know the model intentionally chose no-op vs failed to produce one?
---
## Part 3 — Compare Three Designs
### Option A — Boolean Contract
A single field: `structuralActionRequired: true | false`
| Criterion | Answer |
|-----------|--------|
| Prevents ambiguous semantic-only no-op | PARTIAL — declares intent, but model can always choose the "safe" value without verifying |
| Checks actual mutation | YES — validator compares declared value against proposal shape |
| Requires re-reading English semantics | NO — only compares structured field against structured arrays |
| New schema concept | BOOLEAN |
| Validator complexity | LOW — two boolean checks (true→non-empty, false→empty) |
| Model-compliance risk | MEDIUM — model may default to one value under pressure; binary choice is simplest for the model |
### Option B — Small Action Enum
A field: `semanticAction: "add_new_unknown" | "reuse_or_refine_existing" | "resolve_existing" | "other_structural_mutation" | "no_change_needed"`
| Criterion | Answer |
|-----------|--------|
| Prevents ambiguous semantic-only no-op | PARTIAL — more categories than validation needs, but declares explicit intent |
| Checks actual mutation | YES — validator maps each enum value to specific proposal shape requirements |
| Requires re-reading English semantics | NO — only compares structured field against structured arrays |
| New schema concept | SMALL ENUM (5 values) |
| Validator complexity | MEDIUM — five mapping rules plus cross-validation |
| Model-compliance risk | MEDIUM-HIGH — more categories increase noncompliance risk; model must pick from five options deterministically |
### Option C — Existing Fields Only
No new field. Use `userSupportedMeaning` populated + empty mutation arrays to mean "intentional semantic agreement, no graph change."
| Criterion | Answer |
|-----------|--------|
| Prevents ambiguous semantic-only no-op | PARTIAL — currently rejects this case; treating it as valid would accept noncompliant outputs silently |
| Checks actual mutation | YES — proposal shape is always checkable |
| Requires re-reading English semantics | NO — existing behavior already works without semantic parsing |
| New schema concept | NONE |
| Validator complexity | LOW — no new logic needed |
| Model-compliance risk | HIGH — treating empty-mutation-as-intentional would accept every noncompliant zero-mutation output, making the boundary unenforceable |
---
## Part 4 — The No-Change Case
**Can no-change be verified without re-reading English?**
**YES — but only with a new structured declaration**
With existing fields:
- `userSupportedMeaning` populated + all mutation arrays empty → current code REJECTS
- We cannot distinguish "model intended no-op" from "model forgot to mutate"
- This is NOT verifiable as intentional without knowing what the model *meant*
With a new declaration field:
- Model sets `structuralActionRequired: false` + all mutation arrays empty → validation PASSES (model explicitly declared no action)
- Model sets `structuralActionRequired: true` + all mutation arrays empty → validation REJECTS (contradiction between intent and proposal)
- The declaration itself is the verification mechanism
---
## Part 5 — Relationship to supportCategory
**Relationship: INDEPENDENT OF supportCategory**
Reasoning:
- `supportCategory = "uncertain"` does NOT necessarily mean `add new unknown`
- An equivalent uncertainty may already exist and should be reused (v0.21 identity rule)
- `supportCategory` classifies the *semantic content* of the answer
- The structural action declaration classifies the *proposed graph change*
- These are orthogonal: the same supportCategory can map to different structural actions depending on current graph state
The v0.21 identity rule must be preserved: when an equivalent unresolved uncertainty already exists, reuse/refine that existing node — do not add a duplicate.
---
## Part 6 — Deterministic Invariants
For Option A (boolean contract), the invariants are:
1. **`structuralActionRequired = true` + all mutation arrays empty → REJECT**
The model declared intent for structural action but produced none.
2. **`structuralActionRequired = false` + meaningful mutation present → ACCEPT (diagnostic note)**
Model declared no change but produced one. This is not a contradiction — it may be the model doing extra work beyond what was needed. Log a warning.
3. **`structuralActionRequired` missing + `userSupportedMeaning` populated → REJECT**
Cannot verify intent when required field is absent.
4. **No invariant needed for `structuralActionRequired = false` + empty mutations**
This is the valid "semantic agreement, no structural change" case. The model explicitly declared its intention; validation passes because it can do so deterministically without semantic parsing.
---
## Part 7 — 57J.63 Walkthrough
### Case A: Successful proposal with dedicated unknown
```text
userSupportedMeaning: "uncertainty about projected office savings realism"
proposal: adds dedicated savings-realism unknown
Declaration: structuralActionRequired = true
```
**Why validation passes:**
- Model declares `true` → expects meaningful mutation
- `addedNodes` contains a new unknown node (non-empty)
- Validator compares: declared `true` + actual mutation present → PASS
### Case B: Semantic-only no-op with same meaning
```text
userSupportedMeaning: "uncertainty about projected office savings realism"
proposal: no meaningful graph mutation
Declaration options: structuralActionRequired = false (intentional) or structuralActionRequired = true (noncompliant)
```
**What the model can declare:**
- If equivalent uncertainty already exists in the graph → `structuralActionRequired = false` is valid. The model has legitimately determined no new structure is needed.
- If no equivalent exists and the answer introduces genuinely new material → `structuralActionRequired = true` is required by rule #6.
**Exactly what deterministic validation does:**
1. Check `structuralActionRequired` is populated (not null) because `userSupportedMeaning` is populated
2. Compare declared value against proposal shape:
- `true` + empty mutations → REJECT (contradiction)
- `false` + empty mutations → PASS (explicit no-op declaration validated against zero mutation)
- `false` + non-empty mutations → ACCEPT with diagnostic note (model did more than declared)
**Classification of preferred design:**
**A — ACTUAL CONTRACT ENFORCEMENT**
This is a contract at the structural level: the model declares its intent in a structured field, and code verifies that the proposal shape matches. If the model declares `false` (no change needed), validation passes because it checks the actual empty mutation state — not semantic similarity. The boundary between "intentional no-op" and "noncompliant no-op" is enforced by requiring the explicit declaration.
This solves the boundary because:
- Noncompliant zero-mutation outputs cannot hide behind empty arrays (they must also declare `true`, which fails validation)
- Intentional no-ops are valid when equivalent structure already exists (model declares `false`, validation confirms empty mutation)
---
## Part 8 — Recommendation
**Recommended option: B — boolean structural-action contract**
### Exact new field
```
structuralActionRequired: boolean | null
nullable during transition: YES (but rejected if userSupportedMeaning is populated and field is null)
```
### Location in schema
Add to `answerMeaningSchema` in `lib/graph/schema.js`:
```javascript
export const answerMeaningSchema = z.object({
userSupportedMeaning: z.string().min(1),
possibleInference: z.string().nullable().optional(),
supportCategory: z.enum(...).nullable().optional(),
resolutionGuidance: z.enum(...).nullable().optional(),
structuralActionRequired: z.boolean().nullable().optional(), // NEW
});
```
### Exact validator invariants (in `lib/graph/utils.js`, in `validateGraphUpdate`)
After the existing `hasMeaningfulChange` check (around line 876):
```javascript
if (!hasMeaningfulChange) {
if (update.answerMeaning?.structuralActionRequired === false) {
// Intentional no-op — model declared no change needed, and proposal confirms it
// PASS — this is the "semantic agreement, no structural change" case
} else if (!update.answerMeaning?.structuralActionRequired) {
errors.push(
"answerMeaning.structuralActionRequired must be populated when userSupportedMeaning is present."
);
} else if (update.answerMeaning?.structuralActionRequired === true) {
errors.push(
"answerMeaning.userSupportedMeaning is populated, but the proposal contains no graph mutation. answerMeaning alone does not constitute graph progress."
);
}
}
```
### Transition policy: B — missing new field + populated userSupportedMeaning is rejected
Reason: The current architecture has `userSupportedMeaning` as a commitment signal. If we accept zero-mutation proposals without the new field, every noncompliant output becomes valid again. During transition, reject until the model produces the new field. After the field is present, allow it as the enforcement mechanism.
---
## Convergence
**This is ready for bounded implementation.** The design is minimal: one boolean field and one invariant check. It does not invent new semantic taxonomies. It does not require keyword/synonym logic. It is provider-agnostic because it validates structured output fields, not model behavior.
The boundary it solves: the gap between "model understands the meaning" and "model declares its structural intent deterministically." With this contract, the validator checks a declared boolean against actual proposal shape — no semantic parsing needed.