@quolu/lattice 0.12.2 → 0.12.3
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.
- package/package.json +1 -1
- package/src/bridge-server.mjs +4 -1
- package/src/todo-cli.mjs +10 -10
- package/src/todo-store.mjs +18 -1
package/package.json
CHANGED
package/src/bridge-server.mjs
CHANGED
|
@@ -252,10 +252,13 @@ export async function startBridgeServer({
|
|
|
252
252
|
throw new BridgeConfigError(error?.code === 'EADDRINUSE' ? 'BRIDGE_PORT_UNAVAILABLE' : 'BRIDGE_BIND_FAILED',
|
|
253
253
|
'bridge listen failed', { ...config.listen }, error);
|
|
254
254
|
});
|
|
255
|
+
const boundAddress = server.address();
|
|
256
|
+
const actualPort = typeof boundAddress === 'object' && boundAddress !== null
|
|
257
|
+
? boundAddress.port : config.listen.port;
|
|
255
258
|
let closed = false;
|
|
256
259
|
return Object.freeze({
|
|
257
260
|
address: config.listen.address,
|
|
258
|
-
port:
|
|
261
|
+
port: actualPort,
|
|
259
262
|
updateConfig(next) {
|
|
260
263
|
if (next?.enabled !== true || next.listen.address !== config.listen.address
|
|
261
264
|
|| next.listen.port !== config.listen.port) {
|
package/src/todo-cli.mjs
CHANGED
|
@@ -38,7 +38,7 @@ import {
|
|
|
38
38
|
readTodoStore,
|
|
39
39
|
readTodoStoreStable,
|
|
40
40
|
rebuildTodoSnapshot,
|
|
41
|
-
|
|
41
|
+
verifyEffectivePhaseTodoRevisionSources,
|
|
42
42
|
verifyTodoRevisionSources,
|
|
43
43
|
} from './todo-store.mjs';
|
|
44
44
|
import {
|
|
@@ -803,6 +803,7 @@ async function ensureActiveProjectDashboard({ repoRoot, env }) {
|
|
|
803
803
|
async function verify({ repoRoot, requestedPlanKey }) {
|
|
804
804
|
const store = await readTodoStore({ repoRoot });
|
|
805
805
|
const members = selectMembers(store, requestedPlanKey);
|
|
806
|
+
const verifiedSourceInventories = new Map();
|
|
806
807
|
for (const member of members) {
|
|
807
808
|
const unverified = member.tasks.find((task) => task.evidence_unverified);
|
|
808
809
|
if (unverified !== undefined) {
|
|
@@ -816,12 +817,14 @@ async function verify({ repoRoot, requestedPlanKey }) {
|
|
|
816
817
|
case 'lattice.todo_revision.v1':
|
|
817
818
|
case 'lattice.todo_revision.v2':
|
|
818
819
|
await verifyTodoRevisionSources({ repoRoot, revision: member.revision });
|
|
820
|
+
verifiedSourceInventories.set(member.descriptor.plan_key,
|
|
821
|
+
member.revision.source_inventory);
|
|
819
822
|
break;
|
|
820
823
|
case 'lattice.phase_todo_revision.v1':
|
|
821
824
|
case 'lattice.phase_todo_revision.v2':
|
|
822
|
-
break;
|
|
823
825
|
case 'lattice.phase_todo_revision.v3':
|
|
824
|
-
|
|
826
|
+
verifiedSourceInventories.set(member.descriptor.plan_key,
|
|
827
|
+
await verifyEffectivePhaseTodoRevisionSources({ repoRoot, member }));
|
|
825
828
|
break;
|
|
826
829
|
default:
|
|
827
830
|
throw new TodoStoreError('REVISION_INVALID', 'revision_schema_or_digest_invalid');
|
|
@@ -830,6 +833,7 @@ async function verify({ repoRoot, requestedPlanKey }) {
|
|
|
830
833
|
}
|
|
831
834
|
const verifiedMembers = members.map((member) => {
|
|
832
835
|
const reconciled = member.revision !== null;
|
|
836
|
+
const sourceInventory = verifiedSourceInventories.get(member.descriptor.plan_key) ?? null;
|
|
833
837
|
const phaseRevision = ['lattice.phase_todo_revision.v1', 'lattice.phase_todo_revision.v2',
|
|
834
838
|
'lattice.phase_todo_revision.v3']
|
|
835
839
|
.includes(member.revision?.schema);
|
|
@@ -848,15 +852,11 @@ async function verify({ repoRoot, requestedPlanKey }) {
|
|
|
848
852
|
: todoLegacyReconciliationDigest({ planDigest: member.plan.plan_digest,
|
|
849
853
|
journalHeadDigest: member.journal.events.at(-1).event_digest }),
|
|
850
854
|
source_inventory_count: reconciled
|
|
851
|
-
?
|
|
852
|
-
: member.revision.source_inventory.active.length
|
|
853
|
-
+ member.revision.source_inventory.excluded_tombstones.length : null,
|
|
855
|
+
? sourceInventory.active.length + sourceInventory.excluded_tombstones.length : null,
|
|
854
856
|
active_task_count: reconciled
|
|
855
|
-
?
|
|
856
|
-
? 0 : member.revision.source_inventory.active.length : null,
|
|
857
|
+
? sourceInventory.active.length : null,
|
|
857
858
|
excluded_tombstone_count: reconciled
|
|
858
|
-
?
|
|
859
|
-
? 0 : member.revision.source_inventory.excluded_tombstones.length : null,
|
|
859
|
+
? sourceInventory.excluded_tombstones.length : null,
|
|
860
860
|
};
|
|
861
861
|
});
|
|
862
862
|
const result = {
|
package/src/todo-store.mjs
CHANGED
|
@@ -2552,6 +2552,21 @@ async function predecessorSourceInventory(repoRoot, previous) {
|
|
|
2552
2552
|
return { active: [], excluded_tombstones: [] };
|
|
2553
2553
|
}
|
|
2554
2554
|
|
|
2555
|
+
export async function verifyEffectivePhaseTodoRevisionSources(options = {}) {
|
|
2556
|
+
const repoRoot = path.resolve(options.repoRoot ?? process.cwd());
|
|
2557
|
+
const member = options.member;
|
|
2558
|
+
const revision = member?.revision;
|
|
2559
|
+
if (!validatePhaseTodoRevision(revision)
|
|
2560
|
+
|| canonicalizeTodoArtifact(revision.desired_plan)
|
|
2561
|
+
!== canonicalizeTodoArtifact(member?.plan)) {
|
|
2562
|
+
fail('REVISION_INVALID', 'phase_revision_schema_or_digest_invalid');
|
|
2563
|
+
}
|
|
2564
|
+
const inventory = revision.schema === 'lattice.phase_todo_revision.v3'
|
|
2565
|
+
? revision.source_inventory : await predecessorSourceInventory(repoRoot, member);
|
|
2566
|
+
await verifyRevisionSources(repoRoot, inventory);
|
|
2567
|
+
return inventory;
|
|
2568
|
+
}
|
|
2569
|
+
|
|
2555
2570
|
function validatePhaseV3SourceInventoryDiff(previousInventory, revision) {
|
|
2556
2571
|
const desired = revision.source_inventory;
|
|
2557
2572
|
const previousActive = new Map(previousInventory.active.map((entry) => [entry.task_id, entry]));
|
|
@@ -2655,7 +2670,9 @@ async function applyPhaseTodoRevisionV3(options, revision, repoRoot) {
|
|
|
2655
2670
|
journalHeadDigest: previous.journal.events.at(-1).event_digest });
|
|
2656
2671
|
if (revision.reconciliation.predecessor_reconciliation_digest
|
|
2657
2672
|
!== predecessorReconciliationDigest) fail('STORE_WRITE_CONFLICT', 'stale_predecessor');
|
|
2658
|
-
|
|
2673
|
+
const previousInventory = await predecessorSourceInventory(repoRoot, previous);
|
|
2674
|
+
await verifyRevisionSources(repoRoot, previousInventory);
|
|
2675
|
+
validatePhaseV3SourceInventoryDiff(previousInventory, revision);
|
|
2659
2676
|
await rejectCompetingPhaseV3Transaction(repoRoot, revision);
|
|
2660
2677
|
verifyPlanNarrativeAnchors(repoRoot, revision.desired_plan, previous.plan);
|
|
2661
2678
|
const stateMigration = stateMigrationFor(previous, revision);
|