release-skill 0.9.36 → 0.9.38
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/.claude-plugin/plugin.json +1 -1
- package/.codebuddy-plugin/plugin.json +1 -1
- package/.codex-plugin/plugin.json +2 -2
- package/.cursor-plugin/plugin.json +1 -1
- package/.kimi-plugin/plugin.json +1 -1
- package/.qoder-plugin/plugin.json +1 -1
- package/CHANGELOG.md +56 -0
- package/INSTALL.md +2 -2
- package/INSTALL.zh-CN.md +2 -2
- package/README.md +48 -14
- package/README.zh-CN.md +42 -14
- package/adapters/claude/.claude-plugin/plugin.json +1 -1
- package/adapters/claude/bin/release-skill.bundle.mjs +2020 -1457
- package/adapters/claude/schemas/release-plan.schema.json +1 -0
- package/adapters/claude/schemas/test-intake.schema.json +1 -0
- package/adapters/claude/skills/release-config/SKILL.md +36 -41
- package/adapters/claude/skills/release-docs/SKILL.md +38 -35
- package/adapters/claude/skills/release-help/SKILL.md +59 -54
- package/adapters/claude/skills/release-marketplace/SKILL.md +38 -46
- package/adapters/claude/skills/release-prepare/SKILL.md +38 -27
- package/adapters/claude/skills/release-publish/SKILL.md +38 -44
- package/adapters/claude/skills/release-reconcile/SKILL.md +14 -0
- package/adapters/codex/.codex-plugin/plugin.json +2 -2
- package/adapters/codex/bin/release-skill.bundle.mjs +2020 -1457
- package/adapters/codex/schemas/release-plan.schema.json +1 -0
- package/adapters/codex/schemas/test-intake.schema.json +1 -0
- package/adapters/codex/skills/release-config/SKILL.md +36 -41
- package/adapters/codex/skills/release-docs/SKILL.md +38 -35
- package/adapters/codex/skills/release-help/SKILL.md +59 -54
- package/adapters/codex/skills/release-marketplace/SKILL.md +38 -46
- package/adapters/codex/skills/release-prepare/SKILL.md +38 -27
- package/adapters/codex/skills/release-publish/SKILL.md +38 -44
- package/adapters/codex/skills/release-reconcile/SKILL.md +14 -0
- package/adapters/cursor/.cursor-plugin/plugin.json +1 -1
- package/adapters/cursor/bin/release-skill.bundle.mjs +2020 -1457
- package/adapters/cursor/schemas/release-plan.schema.json +1 -0
- package/adapters/cursor/schemas/test-intake.schema.json +1 -0
- package/adapters/cursor/skills/release-config/SKILL.md +36 -41
- package/adapters/cursor/skills/release-docs/SKILL.md +38 -35
- package/adapters/cursor/skills/release-help/SKILL.md +59 -54
- package/adapters/cursor/skills/release-marketplace/SKILL.md +38 -46
- package/adapters/cursor/skills/release-prepare/SKILL.md +38 -27
- package/adapters/cursor/skills/release-publish/SKILL.md +38 -44
- package/adapters/cursor/skills/release-reconcile/SKILL.md +14 -0
- package/adapters/kimi/.kimi-plugin/plugin.json +1 -1
- package/adapters/kimi/bin/release-skill.bundle.mjs +2020 -1457
- package/adapters/kimi/schemas/release-plan.schema.json +1 -0
- package/adapters/kimi/schemas/test-intake.schema.json +1 -0
- package/adapters/kimi/skills/release-config/SKILL.md +36 -41
- package/adapters/kimi/skills/release-docs/SKILL.md +38 -35
- package/adapters/kimi/skills/release-help/SKILL.md +59 -54
- package/adapters/kimi/skills/release-marketplace/SKILL.md +38 -46
- package/adapters/kimi/skills/release-prepare/SKILL.md +38 -27
- package/adapters/kimi/skills/release-publish/SKILL.md +38 -44
- package/adapters/kimi/skills/release-reconcile/SKILL.md +14 -0
- package/adapters/qoder/.qoder-plugin/plugin.json +1 -1
- package/adapters/qoder/bin/release-skill.bundle.mjs +2020 -1457
- package/adapters/qoder/schemas/release-plan.schema.json +1 -0
- package/adapters/qoder/schemas/test-intake.schema.json +1 -0
- package/adapters/qoder/skills/release-config/SKILL.md +36 -41
- package/adapters/qoder/skills/release-docs/SKILL.md +38 -35
- package/adapters/qoder/skills/release-help/SKILL.md +59 -54
- package/adapters/qoder/skills/release-marketplace/SKILL.md +38 -46
- package/adapters/qoder/skills/release-prepare/SKILL.md +38 -27
- package/adapters/qoder/skills/release-publish/SKILL.md +38 -44
- package/adapters/qoder/skills/release-reconcile/SKILL.md +14 -0
- package/adapters/workbuddy/.codebuddy-plugin/plugin.json +1 -1
- package/adapters/workbuddy/bin/release-skill.bundle.mjs +2020 -1457
- package/adapters/workbuddy/schemas/release-plan.schema.json +1 -0
- package/adapters/workbuddy/schemas/test-intake.schema.json +1 -0
- package/adapters/workbuddy/skills/release-config/SKILL.md +36 -41
- package/adapters/workbuddy/skills/release-docs/SKILL.md +38 -35
- package/adapters/workbuddy/skills/release-help/SKILL.md +59 -54
- package/adapters/workbuddy/skills/release-marketplace/SKILL.md +38 -46
- package/adapters/workbuddy/skills/release-prepare/SKILL.md +38 -27
- package/adapters/workbuddy/skills/release-publish/SKILL.md +38 -44
- package/adapters/workbuddy/skills/release-reconcile/SKILL.md +14 -0
- package/bin/release-skill-cli.mjs +5 -0
- package/bin/release-skill.bundle.mjs +2020 -1457
- package/package.json +1 -1
- package/platform-manifest.json +4 -4
- package/references/01-state-machine.md +26 -0
- package/references/02-project-config.md +108 -7
- package/schemas/release-plan.schema.json +1 -0
- package/schemas/test-intake.schema.json +1 -0
- package/skills/release-config/SKILL.md +36 -41
- package/skills/release-docs/SKILL.md +38 -35
- package/skills/release-help/SKILL.md +59 -54
- package/skills/release-marketplace/SKILL.md +38 -46
- package/skills/release-prepare/SKILL.md +38 -27
- package/skills/release-publish/SKILL.md +38 -44
- package/skills/release-reconcile/SKILL.md +14 -0
- package/skills-src/release-config/SKILL.md +36 -41
- package/skills-src/release-docs/SKILL.md +38 -35
- package/skills-src/release-help/SKILL.md +59 -54
- package/skills-src/release-marketplace/SKILL.md +38 -46
- package/skills-src/release-prepare/SKILL.md +38 -27
- package/skills-src/release-publish/SKILL.md +38 -44
- package/skills-src/release-reconcile/SKILL.md +14 -0
- package/src/commands/approve.mjs +33 -24
- package/src/commands/prepare.mjs +9 -2
- package/src/commands/publish.mjs +10 -2
- package/src/commands/reconcile.mjs +2 -0
- package/src/commands/route.mjs +2 -159
- package/src/commands/verify.mjs +123 -17
- package/src/core/plan-replacement.mjs +371 -0
- package/src/core/pre-authority-failure.mjs +241 -0
- package/src/core/skill-resource-closure.mjs +1 -1
- package/src/core/test-candidate.mjs +15 -67
package/src/commands/route.mjs
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { isProvenPreAuthorityFailure } from '../core/pre-authority-failure.mjs';
|
|
1
2
|
/**
|
|
2
3
|
* release-route command: Deterministic quickstart routing for workflow profiles.
|
|
3
4
|
*
|
|
@@ -46,7 +47,6 @@ import {
|
|
|
46
47
|
validateRunCheckpointMapping,
|
|
47
48
|
} from '../core/run.mjs';
|
|
48
49
|
import { computePlanDigest, validatePlan } from '../core/plan.mjs';
|
|
49
|
-
import { resolveProducerVersion } from '../core/evidence.mjs';
|
|
50
50
|
import { normalizePostPublishView, postPublishActionId } from '../core/postpublish.mjs';
|
|
51
51
|
|
|
52
52
|
const WORKFLOW_KINDS = Object.freeze([
|
|
@@ -732,170 +732,13 @@ async function inspectStateDirectory(statesPath) {
|
|
|
732
732
|
return { ok: true, entries };
|
|
733
733
|
}
|
|
734
734
|
|
|
735
|
-
async function isProvenPreAuthorityFailure(runDir, runName) {
|
|
736
|
-
if (!/^publish-/.test(runName)) return false;
|
|
737
|
-
const summaryPath = join(runDir, 'summary.json');
|
|
738
|
-
const evidencePath = join(runDir, 'evidence.jsonl');
|
|
739
|
-
let summary;
|
|
740
|
-
let events;
|
|
741
|
-
try {
|
|
742
|
-
summary = JSON.parse(await readFile(summaryPath, 'utf8'));
|
|
743
|
-
events = (await readFile(evidencePath, 'utf8')).trim().split('\n').filter(Boolean).map((line) => JSON.parse(line));
|
|
744
|
-
} catch {
|
|
745
|
-
return false;
|
|
746
|
-
}
|
|
747
|
-
|
|
748
|
-
// The v1 writer (used before the producer-bound v2 envelope) created the
|
|
749
|
-
// run directory and appended safety-gate events before its first
|
|
750
|
-
// `release-run.json` state. A failed global preflight then appended only a
|
|
751
|
-
// generic `publish` failure and sealed a summary. This branch deliberately
|
|
752
|
-
// accepts that historical shape only when every byte still proves the same
|
|
753
|
-
// pre-authority boundary. It never infers safety from a directory name,
|
|
754
|
-
// mtime, summary alone, or a loosely similar phase.
|
|
755
|
-
const legacyPreAuthorityPhases = new Set([
|
|
756
|
-
'safety-gate',
|
|
757
|
-
'global-preflight-arbitration',
|
|
758
|
-
're-observe-previous-public-baseline',
|
|
759
|
-
]);
|
|
760
|
-
const legacyInputFailure = /(?:global preflight failed|source authority .*failed|cannot read (?:release plan|approval record)|production (?:publish|commands) require|plan (?:digest mismatch|action completeness gate failed|is not valid|requires)|baseline (?:changed|check failed)|previous-public-baseline|adapter .*failed)/i;
|
|
761
|
-
const legacyUnsafeFailure = /(?:remote (?:tag|branch|release) .*already exists|human intervention required|remote conflict|external write)/i;
|
|
762
|
-
const legacyPreAuthorityGates = new Set([
|
|
763
|
-
'plan-load',
|
|
764
|
-
'plan-schema',
|
|
765
|
-
'plan-digest',
|
|
766
|
-
'action-completeness',
|
|
767
|
-
'approval-load',
|
|
768
|
-
'adapter-availability',
|
|
769
|
-
'baseline-check',
|
|
770
|
-
'source-authority',
|
|
771
|
-
]);
|
|
772
|
-
const validLegacyTimestamp = (value) => typeof value === 'string'
|
|
773
|
-
&& /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d+)?(?:Z|[+-]\d{2}:\d{2})$/.test(value)
|
|
774
|
-
&& Number.isFinite(Date.parse(value));
|
|
775
|
-
const validLegacyError = (error) => error && typeof error === 'object' && !Array.isArray(error)
|
|
776
|
-
&& typeof error.code === 'string' && error.code.length > 0
|
|
777
|
-
&& (error.message === undefined || typeof error.message === 'string');
|
|
778
|
-
const validLegacyEvent = (event, runId) => event && typeof event === 'object' && !Array.isArray(event)
|
|
779
|
-
&& event.schemaVersion === 1
|
|
780
|
-
&& !Object.hasOwn(event, 'producer')
|
|
781
|
-
&& typeof event.runId === 'string' && event.runId === runId
|
|
782
|
-
&& Number.isSafeInteger(event.sequence) && event.sequence >= 1
|
|
783
|
-
&& validLegacyTimestamp(event.timestamp)
|
|
784
|
-
&& event.command === 'publish'
|
|
785
|
-
&& typeof event.phase === 'string'
|
|
786
|
-
&& (legacyPreAuthorityPhases.has(event.phase) || event.phase === 'publish')
|
|
787
|
-
&& typeof event.status === 'string' && event.status.length > 0
|
|
788
|
-
&& (event.error === undefined || event.error === null || validLegacyError(event.error))
|
|
789
|
-
&& (event.duration === undefined || Number.isSafeInteger(event.duration) && event.duration >= 0)
|
|
790
|
-
&& (event.details === undefined || event.details !== null
|
|
791
|
-
&& typeof event.details === 'object' && !Array.isArray(event.details));
|
|
792
|
-
const legacyRunId = basename(runDir);
|
|
793
|
-
const legacyTerminal = events.at(-1);
|
|
794
|
-
const legacyValid = summary && summary.status === 'FAILED'
|
|
795
|
-
&& validLegacyError(summary.error)
|
|
796
|
-
&& typeof summary.error.message === 'string' && legacyInputFailure.test(summary.error.message)
|
|
797
|
-
&& !legacyUnsafeFailure.test(summary.error.message)
|
|
798
|
-
&& (summary.recoveryActionCode === undefined || summary.recoveryActionCode === 'RETRY_COMMAND')
|
|
799
|
-
&& summary.runPath === undefined
|
|
800
|
-
&& summary.finalRunDigest === undefined
|
|
801
|
-
&& summary.latestStatePath === undefined
|
|
802
|
-
&& summary.checkpointStatuses === undefined
|
|
803
|
-
&& Array.isArray(events) && events.length >= 2
|
|
804
|
-
&& events.every((event, index) => validLegacyEvent(event, legacyRunId)
|
|
805
|
-
&& event.sequence === index + 1)
|
|
806
|
-
&& legacyTerminal.phase === 'publish'
|
|
807
|
-
&& legacyTerminal.status === 'failed'
|
|
808
|
-
&& validLegacyError(legacyTerminal.error)
|
|
809
|
-
&& legacyTerminal.error.code === summary.error.code
|
|
810
|
-
&& events.slice(0, -1).filter((event) => event.status === 'failed').every((event) =>
|
|
811
|
-
event.phase === 'safety-gate'
|
|
812
|
-
&& legacyPreAuthorityGates.has(event.gate)
|
|
813
|
-
&& validLegacyError(event.error))
|
|
814
|
-
&& !events.some((event) => event.phase === 'global-preflight-arbitration'
|
|
815
|
-
&& event.status !== 'pre-observe')
|
|
816
|
-
&& !events.slice(0, -1).some((event) => event.phase === 'publish'
|
|
817
|
-
|| /checkpoint|execute|postpublish/i.test(event.phase)
|
|
818
|
-
|| Object.hasOwn(event, 'prePersistedRunPath')
|
|
819
|
-
|| Object.hasOwn(event, 'checkpointCount')
|
|
820
|
-
|| Object.hasOwn(event, 'checkpointStatuses'));
|
|
821
|
-
if (legacyValid) return true;
|
|
822
|
-
|
|
823
|
-
const producerVersion = resolveProducerVersion();
|
|
824
|
-
const sameProducer = (value) => value && typeof value === 'object' && !Array.isArray(value)
|
|
825
|
-
&& Object.keys(value).length === 2
|
|
826
|
-
&& value.name === 'release-skill' && value.version === producerVersion;
|
|
827
|
-
const envelopeKeys = new Set([
|
|
828
|
-
'schemaVersion', 'runId', 'sequence', 'timestamp', 'command', 'producer',
|
|
829
|
-
'phase', 'status', 'error', 'duration', 'details',
|
|
830
|
-
]);
|
|
831
|
-
const validTimestamp = (value) => typeof value === 'string'
|
|
832
|
-
&& /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d+)?(?:Z|[+-]\d{2}:\d{2})$/.test(value)
|
|
833
|
-
&& Number.isFinite(Date.parse(value));
|
|
834
|
-
const detailValidators = {
|
|
835
|
-
unitId: (value) => typeof value === 'string',
|
|
836
|
-
gate: (value) => typeof value === 'string',
|
|
837
|
-
hookName: (value) => typeof value === 'string',
|
|
838
|
-
actionId: (value) => typeof value === 'string',
|
|
839
|
-
step: (value) => typeof value === 'string',
|
|
840
|
-
hookId: (value) => typeof value === 'string',
|
|
841
|
-
tagCommit: (value) => typeof value === 'string',
|
|
842
|
-
cached: (value) => typeof value === 'boolean',
|
|
843
|
-
testSelection: (value) => typeof value === 'string',
|
|
844
|
-
cacheKey: (value) => typeof value === 'string',
|
|
845
|
-
durationMs: (value) => value === null || Number.isSafeInteger(value) && value >= 0,
|
|
846
|
-
exitCode: (value) => Number.isSafeInteger(value),
|
|
847
|
-
findingCount: (value) => Number.isSafeInteger(value) && value >= 0,
|
|
848
|
-
};
|
|
849
|
-
const validDetails = (value) => value === undefined
|
|
850
|
-
|| value !== null && typeof value === 'object' && !Array.isArray(value)
|
|
851
|
-
&& Object.entries(value).every(([key, detail]) => !detailValidators[key] || detailValidators[key](detail));
|
|
852
|
-
const validEnvelope = (event) => event && typeof event === 'object'
|
|
853
|
-
&& Object.keys(event).every((key) => envelopeKeys.has(key))
|
|
854
|
-
&& event.schemaVersion === 2
|
|
855
|
-
&& typeof event.runId === 'string' && event.runId === basename(runDir)
|
|
856
|
-
&& Number.isSafeInteger(event.sequence) && event.sequence >= 1
|
|
857
|
-
&& validTimestamp(event.timestamp)
|
|
858
|
-
&& event.command === 'publish'
|
|
859
|
-
&& typeof event.phase === 'string' && event.phase.length > 0
|
|
860
|
-
&& typeof event.status === 'string' && event.status.length > 0
|
|
861
|
-
&& sameProducer(event.producer)
|
|
862
|
-
&& (event.error === undefined || event.error === null
|
|
863
|
-
|| (typeof event.error === 'object' && !Array.isArray(event.error)
|
|
864
|
-
&& Object.keys(event.error).every((key) => key === 'code' || key === 'message')
|
|
865
|
-
&& typeof event.error.code === 'string' && event.error.code.length > 0
|
|
866
|
-
&& (event.error.message === undefined || typeof event.error.message === 'string')))
|
|
867
|
-
&& (event.duration === undefined || Number.isSafeInteger(event.duration) && event.duration >= 0)
|
|
868
|
-
&& validDetails(event.details);
|
|
869
|
-
if (summary.status !== 'FAILED' || summary.recoveryActionCode !== 'RETRY_COMMAND'
|
|
870
|
-
|| summary.evidencePath !== 'evidence.jsonl' || !sameProducer(summary.producer)
|
|
871
|
-
|| !Array.isArray(events) || events.length === 0) return false;
|
|
872
|
-
let previousSequence = 0;
|
|
873
|
-
let firstFailure = null;
|
|
874
|
-
let genericFailure = null;
|
|
875
|
-
for (const event of events) {
|
|
876
|
-
if (!validEnvelope(event) || event.sequence !== previousSequence + 1) return false;
|
|
877
|
-
previousSequence = event.sequence;
|
|
878
|
-
if (event.status === 'failed' && firstFailure === null) firstFailure = event;
|
|
879
|
-
if (event.phase === 'publish') {
|
|
880
|
-
if (event.status !== 'failed' || genericFailure !== null || event.details?.prePersistedRunPath
|
|
881
|
-
|| event.details?.recoveryActionCode !== summary.recoveryActionCode) return false;
|
|
882
|
-
genericFailure = event;
|
|
883
|
-
} else if (!['safety-gate', 'global-preflight-arbitration'].includes(event.phase)) {
|
|
884
|
-
return false;
|
|
885
|
-
}
|
|
886
|
-
}
|
|
887
|
-
if (!firstFailure || !genericFailure || events.at(-1) !== genericFailure
|
|
888
|
-
|| summary.evidenceSequence !== firstFailure.sequence
|
|
889
|
-
|| summary.stablePhase !== firstFailure.phase) return false;
|
|
890
|
-
return !events.some((event) => event.details?.prePersistedRunPath);
|
|
891
|
-
}
|
|
892
735
|
|
|
893
736
|
/**
|
|
894
737
|
* Postverify is a downstream run type that predates the generic recovery
|
|
895
738
|
* reader. Its completed and open records are consumable only when the summary
|
|
896
739
|
* agrees with the sealed status and the verify parent is fully lineage-valid.
|
|
897
740
|
*/
|
|
898
|
-
async function readLegacyPostverifyRecovery(runPath) {
|
|
741
|
+
export async function readLegacyPostverifyRecovery(runPath) {
|
|
899
742
|
try {
|
|
900
743
|
const run = await loadRun(runPath, { requireDigest: true });
|
|
901
744
|
const acceptedStatuses = new Set(['DISTRIBUTED', 'PARTIAL', 'BLOCKED', 'NEEDS_INPUT']);
|
package/src/commands/verify.mjs
CHANGED
|
@@ -57,7 +57,7 @@ import {
|
|
|
57
57
|
consumePublicSourceAuthorityReceipt,
|
|
58
58
|
verifySourceAuthorityReceipt,
|
|
59
59
|
} from '../core/source-authority.mjs';
|
|
60
|
-
import { verifyFrozenFile } from '../snapshot/frozen.mjs';
|
|
60
|
+
import { computeFrozenSnapshot, resolveFrozenPath, verifyFrozenFile } from '../snapshot/frozen.mjs';
|
|
61
61
|
import {
|
|
62
62
|
deriveBaselineAdvances,
|
|
63
63
|
applyBaselineAdvances,
|
|
@@ -87,6 +87,7 @@ import {
|
|
|
87
87
|
checkSkillResourceClosure,
|
|
88
88
|
createSkillResourceClosureReceipt,
|
|
89
89
|
evaluateConsumerSkillResourceClosureReceipts,
|
|
90
|
+
TRANSPORT_EXCLUSIONS,
|
|
90
91
|
} from '../core/skill-resource-closure.mjs';
|
|
91
92
|
import { deriveSurfaceHostBinding, pluginRootFromManifestRelativePath } from '../core/surface-host-bindings.mjs';
|
|
92
93
|
import { isRemoteWriteAction, isMarketplaceAction } from '../core/checkpoints.mjs';
|
|
@@ -131,6 +132,61 @@ export const VERIFICATION_RESOLVED_TYPES = Object.freeze({
|
|
|
131
132
|
NOT_REQUIRED_UNCHANGED: 'NOT_REQUIRED_UNCHANGED',
|
|
132
133
|
});
|
|
133
134
|
|
|
135
|
+
/** Ignore only write bits; retain type, size, content, and non-write mode bits. */
|
|
136
|
+
const WRITE_BITS = 0o222;
|
|
137
|
+
const PAYLOAD_CONFLICT_REPORT_CAP = 10;
|
|
138
|
+
|
|
139
|
+
function transportIdentityEntries(entries) {
|
|
140
|
+
return entries.map(({ path, type, mode, size, contentDigest }) => ({
|
|
141
|
+
path,
|
|
142
|
+
type,
|
|
143
|
+
mode: mode & ~WRITE_BITS,
|
|
144
|
+
size,
|
|
145
|
+
contentDigest,
|
|
146
|
+
}));
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
function compareInstallToFrozenTransport(frozenEntries, installedEntries) {
|
|
150
|
+
const frozen = transportIdentityEntries(frozenEntries);
|
|
151
|
+
const installed = transportIdentityEntries(installedEntries);
|
|
152
|
+
const installedByPath = new Map(installed.map((entry) => [entry.path, entry]));
|
|
153
|
+
const conflicts = [];
|
|
154
|
+
for (const frozenEntry of frozen) {
|
|
155
|
+
const installedEntry = installedByPath.get(frozenEntry.path);
|
|
156
|
+
if (!installedEntry) {
|
|
157
|
+
conflicts.push(`missing: ${frozenEntry.path}`);
|
|
158
|
+
continue;
|
|
159
|
+
}
|
|
160
|
+
if (installedEntry.type !== frozenEntry.type) {
|
|
161
|
+
conflicts.push(`type mismatch: ${frozenEntry.path}`);
|
|
162
|
+
} else if (
|
|
163
|
+
installedEntry.size !== frozenEntry.size
|
|
164
|
+
|| installedEntry.contentDigest !== frozenEntry.contentDigest
|
|
165
|
+
) {
|
|
166
|
+
conflicts.push(`content mismatch: ${frozenEntry.path}`);
|
|
167
|
+
} else if (installedEntry.mode !== frozenEntry.mode) {
|
|
168
|
+
conflicts.push(`mode mismatch: ${frozenEntry.path}`);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
const frozenPaths = new Set(frozen.map((entry) => entry.path));
|
|
172
|
+
for (const installedEntry of installed) {
|
|
173
|
+
if (!frozenPaths.has(installedEntry.path)) {
|
|
174
|
+
conflicts.push(`extra: ${installedEntry.path}`);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
return conflicts;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
function frozenSnapshotRelativePath(unit, actions, unitId) {
|
|
181
|
+
if (typeof unit?.frozenSnapshot?.path === 'string' && unit.frozenSnapshot.path.length > 0) {
|
|
182
|
+
return unit.frozenSnapshot.path;
|
|
183
|
+
}
|
|
184
|
+
const action = (actions ?? []).find((item) => item.unitId === unitId
|
|
185
|
+
&& typeof item.parameters?.snapshotPath === 'string'
|
|
186
|
+
&& item.parameters.snapshotPath.length > 0);
|
|
187
|
+
return action?.parameters?.snapshotPath ?? null;
|
|
188
|
+
}
|
|
189
|
+
|
|
134
190
|
/** Verify actionless Cursor distributions through their frozen host contract. */
|
|
135
191
|
export async function verifyCursorHostDistributions({
|
|
136
192
|
plan,
|
|
@@ -2265,25 +2321,75 @@ export async function verifyRelease(options) {
|
|
|
2265
2321
|
);
|
|
2266
2322
|
}
|
|
2267
2323
|
// For single-plugin units (the root-surface distributions this
|
|
2268
|
-
// design covers) the install payload IS the frozen snapshot
|
|
2269
|
-
//
|
|
2270
|
-
//
|
|
2271
|
-
//
|
|
2272
|
-
//
|
|
2273
|
-
// publish's recheck uses.
|
|
2324
|
+
// design covers) the install payload IS the frozen snapshot.
|
|
2325
|
+
// Frozen snapshot + original receipt stay on the strict prepare/
|
|
2326
|
+
// publish rule. Install-versus-frozen comparison follows the
|
|
2327
|
+
// existing transport policy (mode & ~0o222) so a sealed 0444
|
|
2328
|
+
// tree may legally reappear as 0644 after marketplace extract.
|
|
2274
2329
|
const pluginDistCount = (surface.unit?.distributions ?? [])
|
|
2275
2330
|
.filter((d) => d.type !== 'npm').length;
|
|
2276
2331
|
if (pluginDistCount === 1 && surface.frozenPluginRoot === '.') {
|
|
2277
|
-
const
|
|
2278
|
-
|
|
2279
|
-
|
|
2280
|
-
|
|
2281
|
-
|
|
2282
|
-
|
|
2283
|
-
|
|
2284
|
-
|
|
2285
|
-
|
|
2286
|
-
|
|
2332
|
+
const frozenRel = frozenSnapshotRelativePath(surface.unit, actions, surface.unitId);
|
|
2333
|
+
if (frozenRel) {
|
|
2334
|
+
const frozenDir = await resolveFrozenPath(root, frozenRel, 'frozen snapshot path');
|
|
2335
|
+
const frozenClosure = await checkSkillResourceClosure({
|
|
2336
|
+
snapshotDir: frozenDir,
|
|
2337
|
+
host: surface.host,
|
|
2338
|
+
surfaceHostBindings: [{ surfaceId: surface.surfaceId, host: surface.host }],
|
|
2339
|
+
});
|
|
2340
|
+
const frozenReceipt = createSkillResourceClosureReceipt(frozenClosure, {
|
|
2341
|
+
unitId: surface.unitId,
|
|
2342
|
+
preparedAt: expectedUnitReceipt.preparedAt ?? null,
|
|
2343
|
+
exitCode: expectedUnitReceipt.exitCode ?? 0,
|
|
2344
|
+
});
|
|
2345
|
+
assertSkillResourceClosureReceipt(
|
|
2346
|
+
expectedUnitReceipt,
|
|
2347
|
+
frozenReceipt,
|
|
2348
|
+
`unit "${surface.unitId}"`,
|
|
2349
|
+
);
|
|
2350
|
+
const frozenSnap = await computeFrozenSnapshot(frozenDir, {
|
|
2351
|
+
excludeRootEntries: TRANSPORT_EXCLUSIONS,
|
|
2352
|
+
});
|
|
2353
|
+
const installedSnap = await computeFrozenSnapshot(surface.installPath, {
|
|
2354
|
+
excludeRootEntries: TRANSPORT_EXCLUSIONS,
|
|
2355
|
+
});
|
|
2356
|
+
const conflicts = compareInstallToFrozenTransport(
|
|
2357
|
+
frozenSnap.entries,
|
|
2358
|
+
installedSnap.entries,
|
|
2359
|
+
);
|
|
2360
|
+
await evidence.append({
|
|
2361
|
+
phase: 'verify',
|
|
2362
|
+
step: 'skill-resource-closure',
|
|
2363
|
+
status: 'install-comparison',
|
|
2364
|
+
unitId: surface.unitId,
|
|
2365
|
+
frozenInputDigest: frozenSnap.digest,
|
|
2366
|
+
installedInputDigest: installedSnap.digest,
|
|
2367
|
+
frozenModes: frozenSnap.entries.map((entry) => ({ path: entry.path, mode: entry.mode })),
|
|
2368
|
+
installedModes: installedSnap.entries.map((entry) => ({ path: entry.path, mode: entry.mode })),
|
|
2369
|
+
});
|
|
2370
|
+
if (conflicts.length > 0) {
|
|
2371
|
+
const listed = conflicts.slice(0, PAYLOAD_CONFLICT_REPORT_CAP).join('; ');
|
|
2372
|
+
const overflow = conflicts.length > PAYLOAD_CONFLICT_REPORT_CAP
|
|
2373
|
+
? `; and ${conflicts.length - PAYLOAD_CONFLICT_REPORT_CAP} more conflicting path(s)`
|
|
2374
|
+
: '';
|
|
2375
|
+
throw new ReleaseError(
|
|
2376
|
+
POST_PUBLISH_VERIFY_FAILED,
|
|
2377
|
+
`installed plugin root differs in path, bytes, size, or non-write mode bits for unit "${surface.unitId}": ${listed}${overflow}`,
|
|
2378
|
+
{ unitId: surface.unitId, conflicts },
|
|
2379
|
+
);
|
|
2380
|
+
}
|
|
2381
|
+
} else {
|
|
2382
|
+
const observedReceipt = createSkillResourceClosureReceipt(closureResult, {
|
|
2383
|
+
unitId: surface.unitId,
|
|
2384
|
+
preparedAt: expectedUnitReceipt.preparedAt ?? null,
|
|
2385
|
+
exitCode: expectedUnitReceipt.exitCode ?? 0,
|
|
2386
|
+
});
|
|
2387
|
+
assertSkillResourceClosureReceipt(
|
|
2388
|
+
expectedUnitReceipt,
|
|
2389
|
+
observedReceipt,
|
|
2390
|
+
`unit "${surface.unitId}"`,
|
|
2391
|
+
);
|
|
2392
|
+
}
|
|
2287
2393
|
}
|
|
2288
2394
|
}
|
|
2289
2395
|
skillResourceClosureReceipts.push(createSkillResourceClosureReceipt(
|