feat(continual-learning): derive wasRealCollision from git overlap (RSI step 3)
Backend becomes authoritative for the verifier signal. recordOutcome now overrides the client-supplied wasRealCollision with deriveWasRealCollision(): a flagged collision counts as REAL only if BOTH named engineers currently have the collided file in their git changedFiles (getGitStates, 120s freshness TTL). Conservative false when the collision is orphaned/missing or git state is stale. Restores the (accepted x wasReal) 2x2 the spec assumes instead of the hardcoded 107/107 true. frontend/useInterventions.ts stops sending a hardcoded `true` (now a backend-overridden placeholder). Spec: continual-learning/spec.md:98-108, policy.md:35-42. backend+frontend typecheck + eslint pass. PLAN.md P0.5 rung 3 added. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -5,7 +5,7 @@ import type {
|
|||||||
InterventionOutcome,
|
InterventionOutcome,
|
||||||
InterventionStatus,
|
InterventionStatus,
|
||||||
} from '@podman/shared';
|
} from '@podman/shared';
|
||||||
import { collections } from './db.js';
|
import { collections, getGitStates } from './db.js';
|
||||||
import { enrichCollisionMemory } from './vectors.js';
|
import { enrichCollisionMemory } from './vectors.js';
|
||||||
|
|
||||||
function comparableFile(raw?: string): string {
|
function comparableFile(raw?: string): string {
|
||||||
@@ -84,13 +84,46 @@ export async function updateInterventionStatus(
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Step 3 — derive whether a flagged collision was REAL from git ground truth,
|
||||||
|
* instead of trusting the client (which historically hardcoded `true`). A
|
||||||
|
* collision counts as real only if BOTH named engineers currently have the
|
||||||
|
* collided file in their git `changedFiles`. Conservative: returns false when
|
||||||
|
* the collision is orphaned/missing or git state is stale/unavailable.
|
||||||
|
* Verifier supervision per docs/continual-learning/spec.md:98-108, policy.md:35-42.
|
||||||
|
*/
|
||||||
|
export async function deriveWasRealCollision(outcome: InterventionOutcome): Promise<boolean> {
|
||||||
|
try {
|
||||||
|
const c = await collections();
|
||||||
|
const collision = await c.collisions.findOne({ id: outcome.collisionId });
|
||||||
|
if (!collision || !Array.isArray(collision.engineers) || collision.engineers.length < 2) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const target = comparableFile(collision.file);
|
||||||
|
if (!target) return false;
|
||||||
|
const gitStates = await getGitStates(outcome.podId);
|
||||||
|
const touchesTarget = (name: string): boolean =>
|
||||||
|
(gitStates.get(name)?.changedFiles ?? []).some((f) => comparableFile(f) === target);
|
||||||
|
return collision.engineers.every(touchesTarget);
|
||||||
|
} catch (err) {
|
||||||
|
console.error(`[memory] wasRealCollision verifier failed: ${(err as Error).message}`);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export async function recordOutcome(outcome: InterventionOutcome): Promise<void> {
|
export async function recordOutcome(outcome: InterventionOutcome): Promise<void> {
|
||||||
|
// Backend is authoritative for wasRealCollision: derive it from git overlap
|
||||||
|
// rather than trusting the client-supplied value. (RSI Step 3)
|
||||||
|
const verified: InterventionOutcome = {
|
||||||
|
...outcome,
|
||||||
|
wasRealCollision: await deriveWasRealCollision(outcome),
|
||||||
|
};
|
||||||
await persist('outcome', async () => {
|
await persist('outcome', async () => {
|
||||||
const c = await collections();
|
const c = await collections();
|
||||||
await c.outcomes.insertOne({ ...outcome });
|
await c.outcomes.insertOne({ ...verified });
|
||||||
await c.interventions.updateOne(
|
await c.interventions.updateOne(
|
||||||
{ id: outcome.interventionId },
|
{ id: verified.interventionId },
|
||||||
{ $set: { status: outcome.accepted ? 'accepted' : 'dismissed' } },
|
{ $set: { status: verified.accepted ? 'accepted' : 'dismissed' } },
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
+15
-5
@@ -509,11 +509,21 @@ no schema change. Owner: RSI track. Independent of the MongoDB-cleanup handoff.
|
|||||||
- Spec: `docs/continual-learning/policy.md:62-63` (prefer prior accepted
|
- Spec: `docs/continual-learning/policy.md:62-63` (prefer prior accepted
|
||||||
kind), `plan.md:66` (second similar event behaves differently).
|
kind), `plan.md:66` (second similar event behaves differently).
|
||||||
|
|
||||||
Follow-ups (separate rungs, not in this change): Step 3 derive
|
3. **Step 3 - derive `wasRealCollision` from git overlap (backend-authoritative)** ✅
|
||||||
`wasRealCollision` from git overlap; Step 4-5 `strategy_versions` +
|
- `backend/src/memory/store.ts` `recordOutcome` now overrides the
|
||||||
Gemini-proposed `LearningProposal` slice; seed a clean demo pod with a repeated
|
client-supplied `wasRealCollision` with `deriveWasRealCollision()`: a
|
||||||
dismissed signature (the historic 85 dismissals are orphaned — `collisionId`
|
collision is real only if BOTH named engineers currently have the collided
|
||||||
resolves to no collision — so they cannot drive the demo verifier).
|
file in their git `changedFiles` (`getGitStates`, 120s freshness TTL).
|
||||||
|
Conservative `false` when orphaned/stale. `frontend/.../useInterventions.ts`
|
||||||
|
stops sending a hardcoded `true` (now a backend-overridden placeholder).
|
||||||
|
- Restores the (accepted × wasReal) 2×2 the spec assumes.
|
||||||
|
- Spec: `docs/continual-learning/spec.md:98-108`, `policy.md:35-42`.
|
||||||
|
|
||||||
|
Follow-ups (separate rungs, not in this change): Step 4-5 `strategy_versions` +
|
||||||
|
Gemini-proposed `LearningProposal` slice; Step 6 durable `owns` write; seed a
|
||||||
|
clean demo pod with a repeated dismissed signature (the historic dismissals are
|
||||||
|
orphaned — `collisionId` resolves to no collision — so they cannot drive the
|
||||||
|
demo verifier).
|
||||||
|
|
||||||
### P1 - polish the money moment
|
### P1 - polish the money moment
|
||||||
|
|
||||||
|
|||||||
@@ -66,7 +66,9 @@ export function useInterventions(room: Room | null) {
|
|||||||
interventionId: active.id,
|
interventionId: active.id,
|
||||||
collisionId: active.collisionId,
|
collisionId: active.collisionId,
|
||||||
podId: active.podId,
|
podId: active.podId,
|
||||||
wasRealCollision: true,
|
// Placeholder only — the backend derives the authoritative value from
|
||||||
|
// git overlap at outcome time (the client cannot know). (RSI Step 3)
|
||||||
|
wasRealCollision: false,
|
||||||
accepted,
|
accepted,
|
||||||
recordedAt: new Date().toISOString(),
|
recordedAt: new Date().toISOString(),
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user