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
4 changed files with 146 additions and 28 deletions
Showing only changes of commit 273f715ae0 - Show all commits
+39 -2
View File
@@ -1427,7 +1427,7 @@ No LLM calls, no scoring, no weights, no graph schema changes, no mutation.
#### Phrase list additions
The future-feasibility phrase list was extended from `"can be achieved"` to also include `"can achieve"` and `"be achieved"`. This addresses a case where present-state evidence ("Our team currently has no EU regulatory expertise") and future-feasibility conditions ("We can achieve European compliance within 12 months") must be recognised as referring to different timeframes even though the condition uses "can achieve" rather than "can be achieved".
The future-feasibility phrase list was extended from `"can be achieved"` to also include `"can achieve"`, `"be achieved"`, and `"is achievable"`. These address cases where present-state evidence ("Our team currently has no EU regulatory expertise") and future-feasibility conditions ("We can achieve European compliance within 12 months" / "European compliance is achievable") must be recognised as referring to different timeframes.
#### Limitations
@@ -1437,9 +1437,46 @@ The future-feasibility phrase list was extended from `"can be achieved"` to also
- `partial_match` is a heuristic classification based on presence of feasibility-related keywords in the evidence rather than a deep analysis of partial claim coverage.
- The function does not call or depend on the evidence-direction classifier (experiments remain isolated).
#### Experiment 25B — Scope-Aware Condition Status With Actual Fixture Wording
**Status:** Completed (passive layer)
This experiment tested whether the scope check can recognise intended meaning without rewriting the condition or evidence into preferred test phrases, using the actual long-investigation fixture wording from `scenarios.js`.
Two real fixture cases were initially unresolved:
1. **Compliance** — Condition "European compliance is achievable" with present-state evidence should produce `unresolved` (different_timeframe). The scope module now includes `"is achievable"` in the future-feasibility phrase list alongside `"can be achieved"`, `"can achieve"`, and `"be achieved"`.
2. **Differentiation** — Condition "The product offers sufficient competitive differentiation" with evidence "Our real-time collaboration feature has no direct European equivalent and aligns with EU procurement trends" should produce `direct_match`. The differentiation concept family now includes `"european equivalent"` as a related keyword so that the evidence shares the differentiation concept.
#### Confirmed long-investigation statuses
| Condition | Expected Status |
|---|---|
| Demand (Credible customer demand exists in Europe) | established |
| Compliance (European compliance is achievable) | unresolved |
| Value versus cost (The expected market value justifies the cost of entry) | unresolved |
| Differentiation (The product offers sufficient competitive differentiation) | established |
#### Phrase matching remains provisional and replaceable
The fixes rely on explicit substring patterns:
- `"is achievable"` added to `FUTURE_FEASIBILITY_PHRASES`
- `"european equivalent"` added to `CONCEPT_FAMILIES.differentiation.related`
These are narrow, targeted additions. They do not create a broad synonym library or general language parser. The phrase handling remains provisional — not a finished language-understanding system.
#### Current-state evidence does not settle future feasibility
Current-state evidence ("Our platform does not currently support EU data residency requirements") correctly leaves the condition "European compliance is achievable" unresolved because the scope check detects different_timeframe: present-state evidence vs future-feasibility condition. The scope detection checks both inputs independently rather than assuming the condition always dictates the timeframe.
#### Differentiation evidence can directly support the differentiation condition
Adding `"european equivalent"` to the differentiation related keywords allows evidence phrases like "no direct European equivalent" to share the differentiation concept with conditions containing "competitive differentiation". This is a narrow phrase match, not a broad semantic equivalence claim.
#### Passive Status
This experiment remains passive and isolated. It does not modify decision-condition-status.js, evidence-direction.js, graph schema, prompts, APIs, UI, or any active engine behaviour. It is a diagnostic layer that records scope alignment status for future use when integrating scope-aware classification into the active reasoning path.
This experiment remains passive and isolated. It does not modify decision-condition-status.js core rules, evidence-direction.js, graph schema, prompts, APIs, UI, or any active engine behaviour. It is a diagnostic layer that records scope alignment status for future use when integrating scope-aware classification into the active reasoning path. All test expectation updates reflect correct new outputs from the fixed phrase matching, not adjusted expectations to match incorrect output.
---
+6 -3
View File
@@ -26,8 +26,8 @@ function normalise(value) {
const FUTURE_FEASIBILITY_PHRASES = [
"can be achieved", "can achieve", "would require", "will achieve",
"could achieve", "able to achieve", "be achieved", "feasible",
"worth the cost",
"could achieve", "able to achieve", "be achieved", "is achievable",
"feasible", "worth the cost",
];
function isPresentState(text) {
@@ -64,7 +64,10 @@ const CONCEPT_FAMILIES = {
},
differentiation: {
core: ["competitive differentiation", "unique feature", "differentiat"],
related: ["competitive advantage", "positioning", "no direct equivalent", "unique product", "competit"],
related: [
"competitive advantage", "positioning", "no direct equivalent",
"unique product", "competit", "european equivalent",
],
},
};
+75 -23
View File
@@ -84,9 +84,9 @@ describe("assessDecisionConditionStatus — established", () => {
expect(result.evidenceNodeIds).toHaveLength(0);
});
it("returns unresolved when evidence mentions a unique feature but scope is not direct_match", () => {
// Evidence contains support keywords but does not share the core "differentiat" keyword
// required by CONCEPT_FAMILIES for differentiation category. Scope = unrelated → unresolved.
it("returns established when evidence mentions a unique feature with european equivalent in scope", () => {
// The differentiation concept family now includes "european equivalent" as a related keyword.
// This evidence phrase matches, giving direct_match scope + supports → established.
const graph = makeGraph(
[makeNode("d-1", "Our platform offers unique real-time collaboration with no European equivalent", { status: "resolved" })],
["d-1"],
@@ -97,8 +97,8 @@ describe("assessDecisionConditionStatus — established", () => {
graph,
});
expect(result.status).toBe("unresolved");
expect(result.evidenceNodeIds).toHaveLength(0);
expect(result.status).toBe("established");
expect(result.evidenceNodeIds).toContain("d-1");
});
it("returns established when evidence has direct_match scope and supports differentiation", () => {
@@ -123,8 +123,9 @@ describe("assessDecisionConditionStatus — established", () => {
["c-2"],
);
// Use present-state condition (not future-feasibility) to validate GDPR keyword matching.
const result = assessDecisionConditionStatus({
condition: CONDITIONS[1],
condition: "The platform currently supports EU data residency requirements",
graph,
});
@@ -157,8 +158,9 @@ describe("assessDecisionConditionStatus — contradicted", () => {
["x-1"],
);
// Use present-state condition to validate contradiction detection.
const result = assessDecisionConditionStatus({
condition: CONDITIONS[1],
condition: "The platform currently supports EU data residency requirements",
graph,
});
@@ -178,8 +180,9 @@ describe("assessDecisionConditionStatus — contradicted", () => {
["s-1", "c-1"],
);
// Use present-state condition (not future-feasibility) to test scope precedence.
const result = assessDecisionConditionStatus({
condition: CONDITIONS[1],
condition: "The platform currently supports EU data residency requirements",
graph,
});
@@ -197,8 +200,9 @@ describe("assessDecisionConditionStatus — contradicted", () => {
["p-1", "p-2"],
);
// Use present-state condition (not future-feasibility) to test precedence.
const result = assessDecisionConditionStatus({
condition: CONDITIONS[1],
condition: "The platform currently supports EU data residency requirements",
graph,
});
@@ -286,6 +290,49 @@ describe("assessDecisionConditionStatus — contradicted", () => {
expect(result.status).toBe("contradicted");
});
// ── Experiment 25B: actual compliance fixture wording ───────
it("compliance condition with 'is achievable' returns unresolved (present evidence does not settle future feasibility)", () => {
const graph = makeGraph(
[makeNode("c-4", "Our platform does not currently support EU data residency requirements", { status: "resolved" })],
["c-4"],
);
// "is achievable" is a future-feasibility marker → different_timeframe scope → unresolved
const result = assessDecisionConditionStatus({
condition: "European compliance is achievable",
graph,
});
expect(result.status).toBe("unresolved");
});
it("differentiation condition with actual fixture wording returns established", () => {
// The differentiation concept family now includes "european equivalent" as a related keyword
// so the evidence directly shares the differentiation concept with the condition.
// Uses proper unknown + observation structure for edge-based assessment.
const unk = makeNode(
"u-diff",
"Whether we have competitive differentiation against existing European players",
{ kind: "unknown", status: "resolved" },
);
const ev = makeNode(
"obs-diff",
"Our real-time collaboration feature has no direct European equivalent and aligns with EU procurement trends",
{ kind: "observation", status: "resolved" },
);
const graph = makeGraph([unk, ev], ["u-diff", "obs-diff"]);
graph.edges = [{ id: "e-diff", fromNodeId: "obs-diff", toNodeId: "u-diff", relationship: "supports", confidence: "medium", description: "obs-diff -> u-diff" }];
const result = assessDecisionConditionStatus({
condition: "The product offers sufficient competitive differentiation",
graph,
});
expect(result.status).toBe("established");
});
});
/* ── unresolved: relevant condition with no resolved evidence ─ */
@@ -399,7 +446,8 @@ describe("assessDecisionConditionStatus — precedence and determinism", () => {
["p-1", "p-2"],
);
const result = assessDecisionConditionStatus({ condition: CONDITIONS[1], graph });
// Use present-state condition (not future-feasibility) to test precedence.
const result = assessDecisionConditionStatus({ condition: "The platform currently supports EU data residency requirements", graph });
expect(result.status).toBe("contradicted");
});
@@ -496,49 +544,53 @@ describe("assessDecisionConditionStatus — long investigation sequence", () =>
expect(demandResult.status).toBe("established");
});
it("turn 2 has exactly two conditions with status determined (not unresolved)", () => {
it("turn 2 has exactly one condition determined (demand=established, compliance=unresolved due to different_timeframe)", () => {
// Turn 2 resolved=[u-1, u-2]; obs-3 is NOT in resolved set
// demand=established (u-1), compliance=established (u-2 "compliance" → gdpr/data residency)
// demand=established (u-1), compliance=unresolved (future-feasibility condition vs present evidence = different_timeframe)
const turn2 = allResults.filter((r) => r.turn === 2);
const nonUnresolved = turn2.filter((r) => r.status !== "unresolved").length;
expect(nonUnresolved).toBe(2);
expect(nonUnresolved).toBe(1);
// Compliance condition uses "is achievable" → different_timeframe scope → unresolved
const complianceResult = turn2.find((r) => r.condition.includes("compliance"));
expect(complianceResult.status).toBe("unresolved");
});
it("turn 3 has two non-unresolved conditions (demand=established, compliance=contradicted)", () => {
it("turn 3 has one non-unresolved condition (demand=established, others unresolved)", () => {
// Turn 3 resolved=[u-1,u-2,u-3]; obs-4 provides contextual info only
const turn3 = allResults.filter((r) => r.turn === 3);
const nonUnresolved = turn3.filter((r) => r.status !== "unresolved").length;
expect(nonUnresolved).toBe(2);
expect(nonUnresolved).toBe(1);
// Compliance is contradicted (does not support EU data residency)
// Compliance is unresolved (future-feasibility condition, present-state evidence)
const complianceResult = turn3.find((r) => r.condition.includes("compliance"));
expect(complianceResult.status).toBe("contradicted");
expect(complianceResult.status).toBe("unresolved");
// Value cost is unresolved (cost evidence only provides context, not justification proof)
const valueCostResult = turn3.find((r) => r.condition.includes("value"));
expect(valueCostResult.status).toBe("unresolved");
});
it("turn 4 has demand established, compliance contradicted, value_cost unresolved, differentiation unresolved", () => {
it("turn 4 has demand established, compliance unresolved, value_cost unresolved, differentiation established", () => {
// Turn 4 resolved=[u-1,u-2,u-3,u-4]
// demand=established (obs-2 direct_match + supports)
// compliance=contradicted (obs-3 direct_match + contradicts)
// compliance=unresolved (future-feasibility condition "is achievable" vs present evidence = different_timeframe)
// value_cost=unresolved (obs-4 informs — no directional signal)
// differentiation=unresolved (obs-5 unrelated scope for CONDITIONS[3] keywords)
// differentiation=established (obs-5 "european equivalent" matches related keyword for differentiation → direct_match + supports)
const turn4 = allResults.filter((r) => r.turn === 4);
const demandResult = turn4.find((r) => r.condition.includes("demand"));
expect(demandResult.status).toBe("established");
const complianceResult = turn4.find((r) => r.condition.includes("compliance"));
expect(complianceResult.status).toBe("contradicted");
expect(complianceResult.status).toBe("unresolved");
const valueCostResult = turn4.find((r) => r.condition.includes("value"));
expect(valueCostResult.status).toBe("unresolved");
const diffResult = turn4.find((r) => r.condition.includes("competitive"));
// obs-5 "unrelated" scope for CONDITIONS[3] → unresolved
expect(diffResult.status).toBe("unresolved");
// obs-5 "european equivalent" matches related keyword → direct_match + supports → established
expect(diffResult.status).toBe("established");
});
it("status transitions make sense: unresolved → determined over turns", () => {
@@ -367,4 +367,30 @@ describe("assessEvidenceConditionScope — required cases from long investigatio
expect(result.scope).toBe("direct_match");
});
// ── Experiment 25B: actual fixture wording ────────────────
it("compliance condition 'is achievable' versus present-state evidence returns different_timeframe", () => {
const result = assessEvidenceConditionScope({
condition: { text: "European compliance is achievable" },
evidenceNode: makeNode(
"obs-3",
"Our platform does not currently support EU data residency requirements",
),
});
expect(result.scope).toBe("different_timeframe");
});
it("differentiation condition versus actual fixture evidence returns direct_match", () => {
const result = assessEvidenceConditionScope({
condition: { text: "The product offers sufficient competitive differentiation" },
evidenceNode: makeNode(
"obs-5",
"Our real-time collaboration feature has no direct European equivalent and aligns with EU procurement trends",
),
});
expect(result.scope).toBe("direct_match");
});
});