@quolu/lattice 0.22.0 → 0.23.0

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@quolu/lattice",
3
- "version": "0.22.0",
3
+ "version": "0.23.0",
4
4
  "description": "Lattice — phase-aware TODO graph compiler and conflict-aware orchestration runtime",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -92,6 +92,27 @@ export async function observeMacosBinaryIdentity(binaryPath) {
92
92
  }
93
93
 
94
94
  /** storeが解決したimmutable bindingからprocess/worktree/checkpointをDirect OSで再観測する。 */
95
+ /**
96
+ * 本repositoryの現在状態を1つのdigestへ畳む。
97
+ *
98
+ * HEAD・作業ツリー状態(untracked/ignoredを含む)・全refを見る。workerがworktreeの外へ
99
+ * 書いてもworktreeのdiffには映らないので、ここだけが本repositoryへの書き込みを捕まえる。
100
+ */
101
+ export async function canonicalRepositoryFingerprint(repoRoot) {
102
+ const parts = [];
103
+ for (const args of [
104
+ ['rev-parse', 'HEAD'],
105
+ ['status', '--porcelain=v1', '--untracked-files=all', '--ignored=matching'],
106
+ ['for-each-ref', '--format=%(refname) %(objectname)'],
107
+ ]) {
108
+ const { stdout } = await execFileAsync('git', args, {
109
+ cwd: repoRoot, encoding: 'utf8', maxBuffer: 32 * 1024 * 1024,
110
+ });
111
+ parts.push(stdout);
112
+ }
113
+ return createHash('sha256').update(parts.join('\u0000'), 'utf8').digest('hex');
114
+ }
115
+
95
116
  export function createDirectOsProcessObserver({ resolveObservationBinding }) {
96
117
  if (typeof resolveObservationBinding !== 'function') fail('SUPERVISOR_CONFIGURATION_INVALID', 'observation binding resolverなし');
97
118
  return async ({ kind, binding, ack }) => {
@@ -104,6 +125,15 @@ export function createDirectOsProcessObserver({ resolveObservationBinding }) {
104
125
  || typeof resolved.base_sha !== 'string' || !/^[0-9a-f]{40}$/.test(resolved.base_sha)
105
126
  || !validateProcessStartIdentity(resolved.process_start_identity)
106
127
  || resolved.process_start_identity.pid !== resolved.process_pid) fail('HOLD_ACKS_INCOMPLETE', 'observation binding不正');
128
+ // 本repositoryを見る材料は対で渡す。片方だけでは照合できず、片方だけを受けると
129
+ // 「検査した」と読める記録が検査なしで作れてしまう。
130
+ const canonicalRoot = resolved.canonical_root ?? null;
131
+ const canonicalBaseline = resolved.canonical_fingerprint_digest ?? null;
132
+ if ((canonicalRoot === null) !== (canonicalBaseline === null)
133
+ || (canonicalRoot !== null && !path.isAbsolute(canonicalRoot))
134
+ || (canonicalBaseline !== null && !/^[0-9a-f]{64}$/u.test(canonicalBaseline))) {
135
+ fail('HOLD_ACKS_INCOMPLETE', 'canonical repository観測bindingが不正');
136
+ }
107
137
  let processState = 'exited';
108
138
  let observedIdentity = resolved.process_start_identity;
109
139
  let processGroupId = resolved.process_group_id;
@@ -145,7 +175,17 @@ export function createDirectOsProcessObserver({ resolveObservationBinding }) {
145
175
  const checkpoint = await captureWorktreeDiff({ worktreePath: worktreeRealpath, baseSha: resolved.base_sha });
146
176
  const processObservation = { schema: 'lattice.direct_process_observation.v1', pid: resolved.process_pid, process_start_identity_digest: observedIdentity.identity_digest, process_group_id: processGroupId, state: processState };
147
177
  const processObservationDigest = digestArtifact(processObservation);
148
- const worktreeFingerprint = { schema: 'lattice.direct_worktree_fingerprint.v1', worktree_id: binding?.worktree_id ?? ack.worktree_id, worktree_realpath: worktreeRealpath, checkpoint_digest: checkpoint.checkpoint_digest };
178
+ // worktreeの外——本repository——への書き込みは、worktreeのdiffにはまったく映らない。
179
+ // 見ていないことを「変更が無かった」と読ませないため、検査したかどうかを記録へ残す
180
+ // (ADR 0140)。渡されていなければ`null`=未検査であり、無変更の主張ではない。
181
+ let canonicalDigest = null;
182
+ if (canonicalRoot !== null) {
183
+ canonicalDigest = await canonicalRepositoryFingerprint(canonicalRoot);
184
+ if (canonicalDigest !== canonicalBaseline) {
185
+ fail('HOLD_ACKS_INCOMPLETE', 'canonical repositoryがworker実行中に変化した');
186
+ }
187
+ }
188
+ const worktreeFingerprint = { schema: 'lattice.direct_worktree_fingerprint.v2', worktree_id: binding?.worktree_id ?? ack.worktree_id, worktree_realpath: worktreeRealpath, checkpoint_digest: checkpoint.checkpoint_digest, canonical_fingerprint_digest: canonicalDigest };
149
189
  const worktreeFingerprintDigest = digestArtifact(worktreeFingerprint);
150
190
  return {
151
191
  quiesced: true,