mirror of
https://github.com/stablyai/orca.git
synced 2026-11-03 02:27:43 +00:00
fix(wsl-mover): sign the manifest as the host parses it
The likelier cause of awin's digest mismatch: the signature covered the exported objects, but the manifest parser keeps only the fields it knows, so a session field it drops (one a sleep can write) left the journal and the host hashing something else. Sign the parsed form, and warn when the parser dropped anything, since such a field does not move. Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -391,6 +391,20 @@ describe('exportWslMoveManifest', () => {
|
||||
})
|
||||
})
|
||||
|
||||
it('signs the session as the host parses it, even with a field the parser drops', () => {
|
||||
// The awin run: a move right after sleeping terminals reset with a digest mismatch, because the
|
||||
// signature covered session fields the manifest parser does not keep.
|
||||
const state = sourceState()
|
||||
const sleptTab: Record<string, unknown> = { ...state.workspaceSession.tabsByWorktree[WORKTREE]![0] }
|
||||
sleptTab.fieldNoParserKeeps = 1
|
||||
// oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: an extra field on a real tab.
|
||||
state.workspaceSession.tabsByWorktree[WORKTREE] = [sleptTab as unknown as TerminalTab]
|
||||
const { manifest } = exportWslMoveManifest(storeFor(state), REQUEST)
|
||||
expect(() =>
|
||||
assertOrcadMigrationManifestDigest(JSON.parse(JSON.stringify(manifest)))
|
||||
).not.toThrow()
|
||||
})
|
||||
|
||||
it('refuses to sign a scope naming a project that is no longer here', () => {
|
||||
expect(() =>
|
||||
exportWslMoveManifest(storeFor(sourceState()), { ...REQUEST, repoIds: ['app', 'gone'] })
|
||||
|
||||
@@ -74,6 +74,8 @@ export class WslMoveScopeChangedError extends Error {
|
||||
}
|
||||
}
|
||||
|
||||
const UNSIGNED_DIGEST = '0'.repeat(64)
|
||||
|
||||
export type WslMoveManifestExportStore = Pick<
|
||||
Store,
|
||||
| 'getRepos'
|
||||
@@ -155,8 +157,9 @@ export function exportWslMoveManifest(
|
||||
{ ...catalog, ...(hasDormantState(dormantState) ? { dormantState } : {}) },
|
||||
rewriter
|
||||
)
|
||||
// Sign the JSON the journal and the host read back: an in-memory Date (a row added this session)
|
||||
// canonicalizes to "{}" here but to its string there, and the host refuses the digest.
|
||||
// Sign what the journal and the host read back: the parsed form (the parser keeps only the fields
|
||||
// it knows, so a session field it drops would break the digest) of the JSON (an in-memory value
|
||||
// with toJSON serializes differently), never the live objects.
|
||||
const unsigned: Omit<OrcadMigrationManifest, 'manifestSha256'> = JSON.parse(
|
||||
JSON.stringify({
|
||||
version: ORCAD_MIGRATION_MANIFEST_VERSION,
|
||||
@@ -167,10 +170,16 @@ export function exportWslMoveManifest(
|
||||
destinationEnvironmentId: request.destinationEnvironmentId
|
||||
})
|
||||
)
|
||||
const manifest = parseOrcadMigrationManifest({
|
||||
const { manifestSha256: _unsigned, ...normalized } = parseOrcadMigrationManifest({
|
||||
...unsigned,
|
||||
manifestSha256: computeOrcadMigrationManifestSha256(unsigned)
|
||||
manifestSha256: UNSIGNED_DIGEST
|
||||
})
|
||||
const manifestSha256 = computeOrcadMigrationManifestSha256(normalized)
|
||||
if (manifestSha256 !== computeOrcadMigrationManifestSha256(unsigned)) {
|
||||
// Not refused (the parsed form is what moves), but a field the parser drops does not move.
|
||||
console.warn('[wsl-move] The manifest parser dropped fields of the exported state.')
|
||||
}
|
||||
const manifest: OrcadMigrationManifest = { ...normalized, manifestSha256 }
|
||||
const worktreeIds = scopedWorktreeIds(repos, { ...catalog, dormantState })
|
||||
const workspaceKeys = [
|
||||
...worktreeIds,
|
||||
|
||||
Reference in New Issue
Block a user