fix(skills): preserve released history across new tags (#9778)
Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
This commit is contained in:
parent
f1c84d3858
commit
ae12bb1292
|
|
@ -423,20 +423,38 @@ async function buildArtifacts() {
|
|||
// Why: released snapshots are the detection ground truth for existing installs,
|
||||
// so a generation-logic change must not rewrite them silently. Only the one
|
||||
// unreleased working-tree append per skill may change between runs.
|
||||
function assertReleasedHistoryPreserved(committedRegistry, artifacts) {
|
||||
function releasedSnapshotCountsFromMapping(releaseMapping) {
|
||||
if (!releaseMapping || releaseMapping.schemaVersion !== RELEASE_MAPPING_SCHEMA_VERSION) {
|
||||
return null
|
||||
}
|
||||
const counts = {}
|
||||
for (const release of releaseMapping.releases ?? []) {
|
||||
for (const [name, revision] of Object.entries(release.skills ?? {})) {
|
||||
counts[name] = Math.max(counts[name] ?? 0, revision)
|
||||
}
|
||||
}
|
||||
return counts
|
||||
}
|
||||
|
||||
function assertReleasedHistoryPreserved(committedRegistry, artifacts, committedReleaseMapping) {
|
||||
if (!committedRegistry || committedRegistry.schemaVersion !== SNAPSHOT_REGISTRY_SCHEMA_VERSION) {
|
||||
return
|
||||
}
|
||||
const committedReleasedCounts = releasedSnapshotCountsFromMapping(committedReleaseMapping)
|
||||
for (const [name, committedSnapshots] of Object.entries(committedRegistry.skills ?? {})) {
|
||||
const releasedCount = artifacts.releasedSnapshotCounts[name] ?? 0
|
||||
const regenerated = artifacts.snapshotRegistry.skills[name] ?? []
|
||||
if (releasedCount < Math.max(0, committedSnapshots.length - 1)) {
|
||||
const minimumReleasedCount =
|
||||
committedReleasedCounts?.[name] ?? Math.max(0, committedSnapshots.length - 1)
|
||||
if (releasedCount < minimumReleasedCount) {
|
||||
throw new Error(
|
||||
`Released snapshot history is incomplete for ${name}. ` +
|
||||
'Fetch all release tags before regenerating skill artifacts.'
|
||||
)
|
||||
}
|
||||
const protectedCount = Math.min(committedSnapshots.length, releasedCount)
|
||||
const protectedCount = committedReleasedCounts
|
||||
? (committedReleasedCounts[name] ?? 0)
|
||||
: Math.min(committedSnapshots.length, releasedCount)
|
||||
for (let index = 0; index < protectedCount; index += 1) {
|
||||
const committed = committedSnapshots[index]
|
||||
const rebuilt = regenerated[index]
|
||||
|
|
@ -458,6 +476,14 @@ async function readCommittedRegistry() {
|
|||
}
|
||||
}
|
||||
|
||||
async function readCommittedReleaseMapping() {
|
||||
try {
|
||||
return JSON.parse(await readFile(RELEASE_MAPPING_PATH, 'utf8'))
|
||||
} catch {
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
function serialized(value) {
|
||||
return `${JSON.stringify(value, null, 2)}\n`
|
||||
}
|
||||
|
|
@ -532,7 +558,11 @@ async function verifyArtifacts(artifacts) {
|
|||
|
||||
async function main() {
|
||||
const artifacts = await buildArtifacts()
|
||||
assertReleasedHistoryPreserved(await readCommittedRegistry(), artifacts)
|
||||
assertReleasedHistoryPreserved(
|
||||
await readCommittedRegistry(),
|
||||
artifacts,
|
||||
await readCommittedReleaseMapping()
|
||||
)
|
||||
await (process.argv.includes('--write') ? writeArtifacts : verifyArtifacts)(artifacts)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -137,6 +137,36 @@ describe('skill bundle manifest generator', () => {
|
|||
expect(() => assertReleasedHistoryPreserved(null, artifacts)).not.toThrow()
|
||||
})
|
||||
|
||||
it('protects only revisions named by the committed release mapping', () => {
|
||||
const snapshot = (releaseRevision, packageDigest) => ({ releaseRevision, packageDigest })
|
||||
const committedRegistry = {
|
||||
schemaVersion: 1,
|
||||
skills: {
|
||||
'linear-tickets': [snapshot(1, 'released'), snapshot(2, 'unreleased-tail')]
|
||||
}
|
||||
}
|
||||
const artifacts = {
|
||||
releasedSnapshotCounts: { 'linear-tickets': 2 },
|
||||
snapshotRegistry: {
|
||||
schemaVersion: 1,
|
||||
skills: { 'linear-tickets': [snapshot(1, 'released'), snapshot(2, 'new-release')] }
|
||||
}
|
||||
}
|
||||
|
||||
expect(() =>
|
||||
assertReleasedHistoryPreserved(committedRegistry, artifacts, {
|
||||
schemaVersion: 1,
|
||||
releases: [{ appVersion: '1.0.0', skills: { 'linear-tickets': 1 } }]
|
||||
})
|
||||
).not.toThrow()
|
||||
expect(() =>
|
||||
assertReleasedHistoryPreserved(committedRegistry, artifacts, {
|
||||
schemaVersion: 1,
|
||||
releases: [{ appVersion: '1.0.0', skills: { 'linear-tickets': 2 } }]
|
||||
})
|
||||
).toThrow('Released snapshot history changed for linear-tickets at revision 2')
|
||||
})
|
||||
|
||||
it('tolerates only redundant trailing release-mapping rows', () => {
|
||||
const serialized = (value) => `${JSON.stringify(value, null, 2)}\n`
|
||||
const rows = [
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@
|
|||
{
|
||||
"name": "linear-tickets",
|
||||
"sourcePath": "skills/linear-tickets",
|
||||
"releaseRevision": 6,
|
||||
"releaseRevision": 7,
|
||||
"packageDigest": "ff9f085631f753f059c631d874177ddd4fa847c5eca85a420dc85fb2bece6ff6",
|
||||
"gitTreeSha": "e35ac3c0c583661983d3fc1352ff3aec74e67e8c",
|
||||
"files": [
|
||||
|
|
@ -94,7 +94,7 @@
|
|||
{
|
||||
"name": "orca-linear",
|
||||
"sourcePath": "skills/orca-linear",
|
||||
"releaseRevision": 4,
|
||||
"releaseRevision": 5,
|
||||
"packageDigest": "5e9622bd3883c0f53e6bd349758096deafceebd2fa260d3e90d677e64d06416d",
|
||||
"gitTreeSha": "f3727995a4719fd522119eca6d1b57542cb5fe23",
|
||||
"files": [
|
||||
|
|
|
|||
|
|
@ -561,6 +561,19 @@
|
|||
"orca-per-workspace-env": 2,
|
||||
"orchestration": 25
|
||||
}
|
||||
},
|
||||
{
|
||||
"appVersion": "1.4.149-rc.2",
|
||||
"skills": {
|
||||
"computer-use": 5,
|
||||
"linear-tickets": 6,
|
||||
"orca-cli": 35,
|
||||
"orca-emulator": 4,
|
||||
"orca-emulator-android": 2,
|
||||
"orca-linear": 4,
|
||||
"orca-per-workspace-env": 2,
|
||||
"orchestration": 25
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1214,6 +1214,22 @@
|
|||
},
|
||||
{
|
||||
"releaseRevision": 6,
|
||||
"packageDigest": "f198d7b22e5ee1673dac403f9cca0553b124e0a90e4fdd05d2c23b7344e32d2b",
|
||||
"gitTreeSha": "de9fc106bbb4e313a90ff9a9513a720909bbd176",
|
||||
"files": [
|
||||
{
|
||||
"path": "SKILL.md",
|
||||
"size": 10596,
|
||||
"executable": false,
|
||||
"classification": "text",
|
||||
"exactSha256": "0c3077c93328b9965430cd6951f1a35889b8c0775037870ea3a8bcf303d9d2c5",
|
||||
"textNormalizedSha256": "0c3077c93328b9965430cd6951f1a35889b8c0775037870ea3a8bcf303d9d2c5",
|
||||
"identitySha256": "0c3077c93328b9965430cd6951f1a35889b8c0775037870ea3a8bcf303d9d2c5"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"releaseRevision": 7,
|
||||
"packageDigest": "ff9f085631f753f059c631d874177ddd4fa847c5eca85a420dc85fb2bece6ff6",
|
||||
"gitTreeSha": "e35ac3c0c583661983d3fc1352ff3aec74e67e8c",
|
||||
"files": [
|
||||
|
|
@ -1280,6 +1296,22 @@
|
|||
},
|
||||
{
|
||||
"releaseRevision": 4,
|
||||
"packageDigest": "d44d09e6ecb6a64da177083aad26a95f031cd1cf26ba059fdc888c2628aef64f",
|
||||
"gitTreeSha": "c34f42030f43e5a85737996fa375bbd79cb5bea8",
|
||||
"files": [
|
||||
{
|
||||
"path": "SKILL.md",
|
||||
"size": 10320,
|
||||
"executable": false,
|
||||
"classification": "text",
|
||||
"exactSha256": "48ded55ec3842ce65105e6db7adf9bc9ed263ece08555cec056e68e90321c3d5",
|
||||
"textNormalizedSha256": "48ded55ec3842ce65105e6db7adf9bc9ed263ece08555cec056e68e90321c3d5",
|
||||
"identitySha256": "48ded55ec3842ce65105e6db7adf9bc9ed263ece08555cec056e68e90321c3d5"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"releaseRevision": 5,
|
||||
"packageDigest": "5e9622bd3883c0f53e6bd349758096deafceebd2fa260d3e90d677e64d06416d",
|
||||
"gitTreeSha": "f3727995a4719fd522119eca6d1b57542cb5fe23",
|
||||
"files": [
|
||||
|
|
|
|||
Loading…
Reference in New Issue