amalgm 0.1.263 → 0.1.264
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/runtime/scripts/amalgm-mcp/registration/index.js +110 -6
- package/runtime/scripts/amalgm-mcp/registry/evidence.js +16 -14
- package/runtime/scripts/amalgm-mcp/tests/entity-materialization.test.js +7 -0
- package/runtime/scripts/amalgm-mcp/tests/registration.service.test.js +39 -0
package/package.json
CHANGED
|
@@ -15,8 +15,10 @@
|
|
|
15
15
|
* (its own future head and rail), never an identity boundary. Every
|
|
16
16
|
* top-level enrolled ground — a workspace, a repository, a single file,
|
|
17
17
|
* or an explicitly added link — is such a tree, with one evidence LENS
|
|
18
|
-
* over its observer root
|
|
19
|
-
* tree
|
|
18
|
+
* over its observer root. A Git territory noticed inside another watched
|
|
19
|
+
* tree is observer-only until an explicit registration admits it; that
|
|
20
|
+
* selected repo is then a tree of its own even if the outer watcher covers
|
|
21
|
+
* its local address. A
|
|
20
22
|
* TREE'S IDENTITY IS ITS UUID, PERMANENT; its root path is only the
|
|
21
23
|
* current local address — lenses are keyed by the observer's rootId
|
|
22
24
|
* (the witness), the observer rescues moved ground by physical
|
|
@@ -66,6 +68,7 @@ const { refusal } = require('./refusal');
|
|
|
66
68
|
const { createRepoFollowers } = require('./repo-followers');
|
|
67
69
|
const { createRepoStates } = require('./repo-states');
|
|
68
70
|
const { registerTree } = require('./tree');
|
|
71
|
+
const { gitDirTruth } = require('../observer/scan');
|
|
69
72
|
|
|
70
73
|
// Events that can change what the recorded edges reach: a new or
|
|
71
74
|
// retargeted pointer, repo ground moving under its card (a worktree
|
|
@@ -112,12 +115,27 @@ function createRegistration(options) {
|
|
|
112
115
|
onSettled: reconcileSettledRepo,
|
|
113
116
|
});
|
|
114
117
|
|
|
115
|
-
/**
|
|
116
|
-
*
|
|
118
|
+
/**
|
|
119
|
+
* A semantic tree is defined by its parentless identity record, never by
|
|
120
|
+
* directory containment. `amalgm add` may materialize a cloud repo inside
|
|
121
|
+
* the local `workspaces/` directory, and `amalgm register` may select a
|
|
122
|
+
* repo already sitting there: both remain their own cloud root. The outer
|
|
123
|
+
* workspace watcher supplies coverage, but it does not own the repo's
|
|
124
|
+
* Card + Checkpoint or identity tree.
|
|
125
|
+
*
|
|
126
|
+
* An unbound observer root is a tree candidate only when no other observer
|
|
127
|
+
* root physically encloses it. That preserves the admission rule for a
|
|
128
|
+
* newly discovered nested repo: Watch can notice it, but only explicit
|
|
129
|
+
* registration can give it a cloud identity and state.
|
|
130
|
+
*/
|
|
117
131
|
function treeRoots() {
|
|
118
132
|
const roots = observer.listRoots();
|
|
119
|
-
return roots.filter((root) =>
|
|
120
|
-
|
|
133
|
+
return roots.filter((root) => {
|
|
134
|
+
const record = rootRecordOf(root.rootId);
|
|
135
|
+
if (record) return record.parentUUID === null;
|
|
136
|
+
return !roots.some((other) => other.rootId !== root.rootId
|
|
137
|
+
&& root.path.startsWith(`${other.path}/`));
|
|
138
|
+
});
|
|
121
139
|
}
|
|
122
140
|
|
|
123
141
|
function treeRootCovering(absPath) {
|
|
@@ -413,6 +431,75 @@ function createRegistration(options) {
|
|
|
413
431
|
}
|
|
414
432
|
}
|
|
415
433
|
|
|
434
|
+
/**
|
|
435
|
+
* A repository selected inside already-watched ground is still a new
|
|
436
|
+
* registered TREE, not a child record of the outer workspace. The outer
|
|
437
|
+
* watcher already knows its territory (or this enrollment establishes that
|
|
438
|
+
* same territory), so the only missing fact is the repo's atomic
|
|
439
|
+
* Card+Checkpoint-backed identity birth.
|
|
440
|
+
*/
|
|
441
|
+
function registerRepositoryInsideCoveredGround(ground, { identityAt = null, suppressJournal = false } = {}) {
|
|
442
|
+
let root = observer.listRoots().find((candidate) => candidate.path === ground) || null;
|
|
443
|
+
if (!root) root = observer.enrollRoot(ground, { scan: false, watch: false });
|
|
444
|
+
if (root.kind !== 'repo') {
|
|
445
|
+
throw new Error(`expected Git repository ground at ${ground}, found ${root.kind}`);
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
const bound = memory.boundTo(root.rootId);
|
|
449
|
+
if (bound !== undefined) {
|
|
450
|
+
const record = registry.record(bound);
|
|
451
|
+
if (record?.type !== 'repo.git') {
|
|
452
|
+
throw new Error(`repository watcher ${ground} is bound to non-repository entity ${bound}`);
|
|
453
|
+
}
|
|
454
|
+
if (record.parentUUID !== null) {
|
|
455
|
+
// This is the explicit nested-boundary case created as part of an
|
|
456
|
+
// already-registered enclosing repo. It already has a complete
|
|
457
|
+
// Card+Checkpoint state and cannot be duplicated at one address.
|
|
458
|
+
requireLiveWatchers(ground);
|
|
459
|
+
return { rootPath: ground, record };
|
|
460
|
+
}
|
|
461
|
+
requireLiveWatchers(ground);
|
|
462
|
+
return { rootPath: ground, record };
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
try {
|
|
466
|
+
requireWatchBirth(root);
|
|
467
|
+
assertWitnessed({ observer, registry, memory });
|
|
468
|
+
const register = () => registry.transaction(() => {
|
|
469
|
+
registerTree({
|
|
470
|
+
root,
|
|
471
|
+
registry,
|
|
472
|
+
memory,
|
|
473
|
+
shouldEnroll: observer.shouldEnroll,
|
|
474
|
+
captureRepo,
|
|
475
|
+
repoStates,
|
|
476
|
+
repoStateAt,
|
|
477
|
+
identityAt,
|
|
478
|
+
});
|
|
479
|
+
assertWatchedThroughCommit(root.path);
|
|
480
|
+
});
|
|
481
|
+
if (suppressJournal) registry.withoutJournal(register);
|
|
482
|
+
else register();
|
|
483
|
+
} catch (error) {
|
|
484
|
+
// A root that existed only because the outer watcher discovered its
|
|
485
|
+
// territory must remain coverage after a refused explicit admission;
|
|
486
|
+
// do not remove it (the parent still owns the OS watcher). A root this
|
|
487
|
+
// call itself enrolled has no durable identity and can safely retire.
|
|
488
|
+
throw error;
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
const startWatch = () => {
|
|
492
|
+
observer.start(root.rootId);
|
|
493
|
+
ensureTrees();
|
|
494
|
+
reconcileInitialRepoWorktrees(root.path);
|
|
495
|
+
flushIfDirty();
|
|
496
|
+
requireLiveWatchers(root.path);
|
|
497
|
+
};
|
|
498
|
+
if (suppressJournal) registry.withoutJournal(startWatch);
|
|
499
|
+
else startWatch();
|
|
500
|
+
return { rootPath: root.path, record: rootRecordOf(root.rootId) };
|
|
501
|
+
}
|
|
502
|
+
|
|
416
503
|
/** Real ground -> the UUID of the entity that owns it, across trees. */
|
|
417
504
|
function resolveGround(real) {
|
|
418
505
|
const home = treeRootCovering(real);
|
|
@@ -542,6 +629,23 @@ function createRegistration(options) {
|
|
|
542
629
|
}
|
|
543
630
|
const covering = treeRootCovering(ground);
|
|
544
631
|
if (covering) {
|
|
632
|
+
// A repository has a different state adapter from the workspace that
|
|
633
|
+
// happens to contain its local address. Its observer territory may
|
|
634
|
+
// already exist, but it is not registered until this explicit command
|
|
635
|
+
// creates the parentless repo tree with its initial Card + Checkpoint.
|
|
636
|
+
// The same branch is used by cloud `add`: materialized repos commonly
|
|
637
|
+
// land under the default `workspaces/` directory while retaining their
|
|
638
|
+
// cloud root UUID and parentless graph position.
|
|
639
|
+
const isRepository = fs.lstatSync(ground).isDirectory() && gitDirTruth(ground) === 'present';
|
|
640
|
+
if (isRepository) {
|
|
641
|
+
const exact = observer.listRoots().find((root) => root.path === ground);
|
|
642
|
+
const known = exact && memory.boundTo(exact.rootId) !== undefined
|
|
643
|
+
? registry.record(memory.boundTo(exact.rootId))
|
|
644
|
+
: null;
|
|
645
|
+
if (!known || known.type !== 'repo.git' || known.parentUUID === null) {
|
|
646
|
+
return registerRepositoryInsideCoveredGround(ground, { identityAt, suppressJournal });
|
|
647
|
+
}
|
|
648
|
+
}
|
|
545
649
|
// This address is already registered ground. Its prior Register →
|
|
546
650
|
// Watch transition remains the proof of its identity; repeating a
|
|
547
651
|
// harmless "ensure the safe home exists" request must not rescan every
|
|
@@ -80,7 +80,8 @@
|
|
|
80
80
|
* the folder record and every descendant's derived address follows,
|
|
81
81
|
* which is why folders reconcile before files, parents before
|
|
82
82
|
* children;
|
|
83
|
-
* - repo territories as
|
|
83
|
+
* - repo territories as observer truth, with an explicitly registered
|
|
84
|
+
* repository boundary supplying the corresponding repo.git record;
|
|
84
85
|
* - ADOPTION, the channel law completing across trees: unclaimed
|
|
85
86
|
* ground wearing the physical identity of an orphaned top-level tree
|
|
86
87
|
* is that tree's arrival — one lawful parent update on its
|
|
@@ -604,23 +605,24 @@ function createEvidence(options) {
|
|
|
604
605
|
};
|
|
605
606
|
settleClaims(claimsOf(desiredDirs, 'dir'), 'dir');
|
|
606
607
|
|
|
607
|
-
// Territories after folder continuity, before file ground
|
|
608
|
-
// boundary is
|
|
609
|
-
//
|
|
608
|
+
// Territories after folder continuity, before file ground. A Git
|
|
609
|
+
// boundary is NOT ordinary filesystem evidence: every repo.git record
|
|
610
|
+
// needs a Card + Checkpoint state and immutable transport before it can
|
|
611
|
+
// travel. This generic lens has neither, so it may only recognize a
|
|
612
|
+
// boundary that the registration adapter already made whole. An
|
|
613
|
+
// unregistered repository remains a local observer territory until
|
|
614
|
+
// `amalgm register <repo>` admits it through that adapter.
|
|
615
|
+
//
|
|
616
|
+
// This is deliberately not a convenience gate. Letting this layer
|
|
617
|
+
// mint or promote repo.git would create an identity record whose cloud
|
|
618
|
+
// head names no state — the exact invalid half-repository the cloud
|
|
619
|
+
// contract rejects. Folders and files are evidence-shaped; Git is a
|
|
620
|
+
// type-specific state carrier.
|
|
610
621
|
for (const territory of [...territories].sort()) {
|
|
611
622
|
const parentUuid = ensureDir(path.dirname(territory) === '.' ? '' : path.dirname(territory), root);
|
|
612
623
|
const name = path.basename(territory);
|
|
613
624
|
const at = registry.activeAt(parentUuid, name);
|
|
614
|
-
if (
|
|
615
|
-
// Adoption before birth here too: a registered top-level repo
|
|
616
|
-
// TREE that moved in arrives as its own record, never a fresh
|
|
617
|
-
// territory record. The enclosed observer root's identity is
|
|
618
|
-
// the evidence — the same physical dir the orphaned tree owns.
|
|
619
|
-
const enclosed = observer.listRoots().find((row) => row.path === path.join(groundPath, territory));
|
|
620
|
-
const arrivedRecord = enclosed ? arrivedTree(enclosed.device, enclosed.inode, REPO_TREE) : null;
|
|
621
|
-
if (arrivedRecord) registry.move(arrivedRecord.uuid, { parentUUID: parentUuid, name });
|
|
622
|
-
else registry.create({ type: 'repo.git', parentUUID: parentUuid, name });
|
|
623
|
-
} else if (at.type !== 'repo.git') registry.changeType(at.uuid, 'repo.git');
|
|
625
|
+
if (at?.type !== 'repo.git') continue;
|
|
624
626
|
}
|
|
625
627
|
// A lifted lens is the reverse transition on the same record.
|
|
626
628
|
for (const { record, rel } of walkActive(root.uuid, '', [])) {
|
|
@@ -369,6 +369,11 @@ test('add recreates an actual Git repository from verified Repocard cargo, then
|
|
|
369
369
|
snapshotChecksum: sha256(snapshotBytes),
|
|
370
370
|
});
|
|
371
371
|
|
|
372
|
+
// This is the real user destination: a repo lands below an already-live
|
|
373
|
+
// local workspaces-like container. Physical containment supplies watcher
|
|
374
|
+
// coverage only; the cloud repo must retain its parentless root identity.
|
|
375
|
+
target.registration.add(targetParent, { suppressJournal: true });
|
|
376
|
+
|
|
372
377
|
const planned = source.entityCloud.contentHeads(records);
|
|
373
378
|
assert.deepEqual(
|
|
374
379
|
planned.map(({ contentHash }) => contentHash),
|
|
@@ -406,6 +411,8 @@ test('add recreates an actual Git repository from verified Repocard cargo, then
|
|
|
406
411
|
const expected = source.registration.repoState(registered.record.uuid);
|
|
407
412
|
assert.equal(verify(destination, expected).ok, true, 'the downloaded Git state reproduces its Card + Checkpoint IDs');
|
|
408
413
|
assert.equal(target.registration.registry.syncRecord(registered.record.uuid).uuid, registered.record.uuid);
|
|
414
|
+
assert.equal(target.registration.registry.syncRecord(registered.record.uuid).parentUUID, null,
|
|
415
|
+
'a repo materialized beneath an existing local workspace keeps its cloud root position');
|
|
409
416
|
assert.equal(target.observer.status().find((entry) => entry.path === fs.realpathSync(destination))?.watching, true,
|
|
410
417
|
'the same successful add starts the destination repository watcher');
|
|
411
418
|
|
|
@@ -724,6 +724,45 @@ test('a nested repository has its own baseline and watcher, while its tree remai
|
|
|
724
724
|
assert.equal(tree.registry.activeAt(src.uuid, 'live.js').type, 'file.text');
|
|
725
725
|
});
|
|
726
726
|
|
|
727
|
+
test('a repo discovered inside watched workspace ground stays unregistered until explicit register gives it one complete repo root', async () => {
|
|
728
|
+
const service = makeService('workspace-repo-admission');
|
|
729
|
+
const workspace = makeGround('workspace-repo-admission');
|
|
730
|
+
const { record: workspaceRecord } = await service.register(workspace);
|
|
731
|
+
const repo = path.join(workspace, 't3code');
|
|
732
|
+
fs.mkdirSync(repo);
|
|
733
|
+
execFileSync('git', ['init', '-q'], { cwd: repo });
|
|
734
|
+
fs.writeFileSync(path.join(repo, 'readme.md'), '# T3\n');
|
|
735
|
+
|
|
736
|
+
// This is the production ordering that convicted the live Fly run: the
|
|
737
|
+
// outer workspace watcher discovers a newly cloned repository first.
|
|
738
|
+
const workspaceRoot = service.observer.listRoots().find((root) => root.path === workspace);
|
|
739
|
+
service.observer.scanNow(workspaceRoot.rootId);
|
|
740
|
+
await new Promise((resolve) => setImmediate(resolve));
|
|
741
|
+
service.registration.flushIfDirty();
|
|
742
|
+
|
|
743
|
+
const territory = service.observer.listRoots().find((root) => root.path === repo);
|
|
744
|
+
assert.equal(territory?.kind, 'repo', 'Watch knows the local Git territory');
|
|
745
|
+
assert.notEqual(
|
|
746
|
+
service.registration.tree(workspace).registry.activeAt(workspaceRecord.uuid, 't3code')?.type,
|
|
747
|
+
'repo.git',
|
|
748
|
+
'generic filesystem evidence never mints a half-repository with no Card + Checkpoint',
|
|
749
|
+
);
|
|
750
|
+
|
|
751
|
+
const { record } = await service.register(repo);
|
|
752
|
+
assert.equal(record.type, 'repo.git');
|
|
753
|
+
assert.equal(record.parentUUID, null,
|
|
754
|
+
'selected repo is its own cloud tree even though its local address is inside workspaces');
|
|
755
|
+
assert.equal(service.registration.tree(repo).root().uuid, record.uuid,
|
|
756
|
+
'the nested observer root now witnesses that selected tree');
|
|
757
|
+
assert.ok(service.registration.repoState(record.uuid)?.stateId,
|
|
758
|
+
'its identity and initial Card + Checkpoint were committed together');
|
|
759
|
+
assert.equal(
|
|
760
|
+
service.observer.status().some((row) => row.path === repo && row.watching && !row.degraded),
|
|
761
|
+
true,
|
|
762
|
+
'the selected repo is live after the atomic registration',
|
|
763
|
+
);
|
|
764
|
+
});
|
|
765
|
+
|
|
727
766
|
test('a reopened repo watcher resumes from the saved Card + Checkpoint baseline', async () => {
|
|
728
767
|
const service = makeService('repo-reopen');
|
|
729
768
|
const repo = makeGround('repo-reopen');
|