release-skill 0.8.1 → 0.9.1
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/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +1 -1
- package/.codebuddy-plugin/plugin.json +1 -1
- package/.codex-plugin/plugin.json +2 -2
- package/.kimi-plugin/plugin.json +1 -1
- package/CHANGELOG.md +63 -0
- package/INSTALL.md +27 -2
- package/INSTALL.zh-CN.md +19 -2
- package/README.md +41 -22
- package/README.zh-CN.md +34 -20
- package/adapters/claude/.claude-plugin/marketplace.json +1 -1
- package/adapters/claude/.claude-plugin/plugin.json +1 -1
- package/adapters/claude/bin/consumer-contract-vectors.json +110 -0
- package/adapters/claude/bin/error-codes.json +8 -1
- package/adapters/claude/bin/foundation-resource-binding.json +1 -1
- package/adapters/claude/bin/registry.json +1 -1
- package/adapters/claude/bin/release-skill-local-finish.mjs +4 -0
- package/adapters/claude/bin/release-skill.bundle.mjs +3955 -2431
- package/adapters/claude/bin/rules.json +1 -1
- package/adapters/claude/bin/schemas/consumer-contract-vector.schema.json +149 -0
- package/adapters/claude/schemas/release-plan.schema.json +40 -3
- package/adapters/claude/skills/release-finish/SKILL.md +65 -0
- package/adapters/claude/skills/release-help/SKILL.md +3 -2
- package/adapters/claude/skills/release-prepare/SKILL.md +2 -2
- package/adapters/claude/skills/release-publish/SKILL.md +3 -1
- package/adapters/claude/skills/release-verify/SKILL.md +6 -1
- package/adapters/claude/src/schemas/migration-manifest.schema.json +271 -7
- package/adapters/claude/src/schemas/profile-adoption-declaration.schema.json +8 -0
- package/adapters/codex/.codex-plugin/plugin.json +2 -2
- package/adapters/codex/bin/consumer-contract-vectors.json +110 -0
- package/adapters/codex/bin/error-codes.json +8 -1
- package/adapters/codex/bin/foundation-resource-binding.json +1 -1
- package/adapters/codex/bin/registry.json +1 -1
- package/adapters/codex/bin/release-skill-local-finish.mjs +4 -0
- package/adapters/codex/bin/release-skill.bundle.mjs +3955 -2431
- package/adapters/codex/bin/rules.json +1 -1
- package/adapters/codex/bin/schemas/consumer-contract-vector.schema.json +149 -0
- package/adapters/codex/schemas/release-plan.schema.json +40 -3
- package/adapters/codex/skills/release-finish/SKILL.md +72 -0
- package/adapters/codex/skills/release-help/SKILL.md +3 -2
- package/adapters/codex/skills/release-prepare/SKILL.md +2 -2
- package/adapters/codex/skills/release-publish/SKILL.md +3 -1
- package/adapters/codex/skills/release-verify/SKILL.md +6 -1
- package/adapters/codex/src/schemas/migration-manifest.schema.json +271 -7
- package/adapters/codex/src/schemas/profile-adoption-declaration.schema.json +8 -0
- package/adapters/kimi/.kimi-plugin/plugin.json +1 -1
- package/adapters/kimi/bin/consumer-contract-vectors.json +110 -0
- package/adapters/kimi/bin/error-codes.json +8 -1
- package/adapters/kimi/bin/foundation-resource-binding.json +1 -1
- package/adapters/kimi/bin/registry.json +1 -1
- package/adapters/kimi/bin/release-skill-local-finish.mjs +4 -0
- package/adapters/kimi/bin/release-skill.bundle.mjs +3955 -2431
- package/adapters/kimi/bin/rules.json +1 -1
- package/adapters/kimi/bin/schemas/consumer-contract-vector.schema.json +149 -0
- package/adapters/kimi/schemas/release-plan.schema.json +40 -3
- package/adapters/kimi/skills/release-finish/SKILL.md +72 -0
- package/adapters/kimi/skills/release-help/SKILL.md +3 -2
- package/adapters/kimi/skills/release-prepare/SKILL.md +2 -2
- package/adapters/kimi/skills/release-publish/SKILL.md +3 -1
- package/adapters/kimi/skills/release-verify/SKILL.md +6 -1
- package/adapters/kimi/src/schemas/migration-manifest.schema.json +271 -7
- package/adapters/kimi/src/schemas/profile-adoption-declaration.schema.json +8 -0
- package/adapters/workbuddy/.codebuddy-plugin/plugin.json +1 -1
- package/adapters/workbuddy/bin/consumer-contract-vectors.json +110 -0
- package/adapters/workbuddy/bin/error-codes.json +8 -1
- package/adapters/workbuddy/bin/foundation-resource-binding.json +1 -1
- package/adapters/workbuddy/bin/registry.json +1 -1
- package/adapters/workbuddy/bin/release-skill-local-finish.mjs +4 -0
- package/adapters/workbuddy/bin/release-skill.bundle.mjs +3955 -2431
- package/adapters/workbuddy/bin/rules.json +1 -1
- package/adapters/workbuddy/bin/schemas/consumer-contract-vector.schema.json +149 -0
- package/adapters/workbuddy/schemas/release-plan.schema.json +40 -3
- package/adapters/workbuddy/skills/release-finish/SKILL.md +65 -0
- package/adapters/workbuddy/skills/release-help/SKILL.md +3 -2
- package/adapters/workbuddy/skills/release-prepare/SKILL.md +2 -2
- package/adapters/workbuddy/skills/release-publish/SKILL.md +3 -1
- package/adapters/workbuddy/skills/release-verify/SKILL.md +6 -1
- package/adapters/workbuddy/src/schemas/migration-manifest.schema.json +271 -7
- package/adapters/workbuddy/src/schemas/profile-adoption-declaration.schema.json +8 -0
- package/bin/consumer-contract-vectors.json +110 -0
- package/bin/error-codes.json +8 -1
- package/bin/foundation-resource-binding.json +1 -1
- package/bin/registry.json +1 -1
- package/bin/release-skill-cli.mjs +163 -2
- package/bin/release-skill-local-finish.mjs +4 -0
- package/bin/release-skill.bundle.mjs +3955 -2431
- package/bin/rules.json +1 -1
- package/bin/schemas/consumer-contract-vector.schema.json +149 -0
- package/package.json +4 -4
- package/platform-manifest.json +31 -5
- package/schemas/release-plan.schema.json +40 -3
- package/skills/release-finish/SKILL.md +65 -0
- package/skills/release-help/SKILL.md +3 -2
- package/skills/release-prepare/SKILL.md +2 -2
- package/skills/release-publish/SKILL.md +3 -1
- package/skills/release-verify/SKILL.md +6 -1
- package/skills-src/release-finish/SKILL.md +65 -0
- package/skills-src/release-help/SKILL.md +3 -2
- package/skills-src/release-prepare/SKILL.md +2 -2
- package/skills-src/release-publish/SKILL.md +3 -1
- package/skills-src/release-verify/SKILL.md +6 -1
- package/src/commands/approve.mjs +14 -2
- package/src/commands/distribute.mjs +562 -321
- package/src/commands/post-release-local.mjs +802 -0
- package/src/commands/postverify.mjs +262 -155
- package/src/commands/prepare.mjs +106 -56
- package/src/commands/publish.mjs +11 -10
- package/src/commands/reconcile.mjs +11 -10
- package/src/commands/setup.mjs +26 -13
- package/src/commands/ship.mjs +169 -28
- package/src/commands/verify.mjs +19 -8
- package/src/core/adoption-assessment.mjs +0 -3
- package/src/core/approval.mjs +11 -8
- package/src/core/derived-artifact-gates.mjs +32 -5
- package/src/core/hooks.mjs +0 -16
- package/src/core/plan.mjs +19 -11
- package/src/core/postpublish-approval.mjs +12 -2
- package/src/core/postpublish-bundle.mjs +51 -13
- package/src/core/postpublish.mjs +146 -6
- package/src/core/preset-executor.mjs +53 -0
- package/src/core/proposal-inbox.mjs +91 -27
- package/src/core/recovery.mjs +27 -12
- package/src/producers/build-adapters.mjs +10 -1
- package/src/producers/foundation-resource-projection.mjs +16 -3
- package/src/schemas/migration-manifest.schema.json +271 -7
- package/src/schemas/profile-adoption-declaration.schema.json +8 -0
package/src/commands/prepare.mjs
CHANGED
|
@@ -28,7 +28,7 @@ const execFile = promisify(execFileCb);
|
|
|
28
28
|
import { classifyPathInput, writeFileAtomic } from 'skill-family-harness-node';
|
|
29
29
|
import { loadProjectConfig } from '../core/config.mjs';
|
|
30
30
|
import { captureBaseline } from '../core/baseline.mjs';
|
|
31
|
-
import {
|
|
31
|
+
import { runHook } from '../core/hooks.mjs';
|
|
32
32
|
import { computeHookCacheKey, readHookCache, writeHookCache } from '../core/hook-cache.mjs';
|
|
33
33
|
import {
|
|
34
34
|
assertExpectedPublicSurface,
|
|
@@ -57,7 +57,11 @@ import {
|
|
|
57
57
|
} from '../snapshot/frozen.mjs';
|
|
58
58
|
import { ReleaseError, GATE_FAILED, CONFIG_INVALID, CONFIG_MISSING, FORBIDDEN_CONTENT_DETECTED, RELEASE_DOCS_STALE, DIRTY_SOURCE_INPUT, BUNDLE_STALE } from '../core/errors.mjs';
|
|
59
59
|
import { assertBundleFreshness } from '../core/bundle-freshness.mjs';
|
|
60
|
-
import {
|
|
60
|
+
import {
|
|
61
|
+
assertAdapterFreshness,
|
|
62
|
+
assertPlatformManifestFreshness,
|
|
63
|
+
assertSelfBootstrapFacts,
|
|
64
|
+
} from '../core/derived-artifact-gates.mjs';
|
|
61
65
|
import { isFoundationPluginVerificationEligible } from '../core/foundation-plugin-verification.mjs';
|
|
62
66
|
import { PKG_ROOT } from '../core/pkg-root.mjs';
|
|
63
67
|
import { writeFrozenMarker, FROZEN_MARKER_FILENAME } from '../core/frozen-marker.mjs';
|
|
@@ -81,6 +85,7 @@ import {
|
|
|
81
85
|
validatePostPublishDeclaration,
|
|
82
86
|
normalizePostPublishDeclaration,
|
|
83
87
|
orderNormalizedHooks,
|
|
88
|
+
validatePostPublishHookIdUniqueness,
|
|
84
89
|
PAYLOAD_SOURCE_TAG_WORKTREE,
|
|
85
90
|
} from '../core/postpublish.mjs';
|
|
86
91
|
import { freezeExecutionBundle, bundleRootForAuthorityDir } from '../core/postpublish-bundle.mjs';
|
|
@@ -3024,8 +3029,9 @@ export async function prepareRelease(options) {
|
|
|
3024
3029
|
// --- Step 1b-fast: version-sensitive derived-artifact fast pre-gates (O1) ---
|
|
3025
3030
|
// 2026-08-18 investigation §3.2: adapter drift and stale self-bootstrap
|
|
3026
3031
|
// fact pins used to surface only deep inside the ~80s full test hook.
|
|
3027
|
-
// Promote the
|
|
3028
|
-
// release-docs-self-bootstrap
|
|
3032
|
+
// Promote the three exact canonical checks (build-adapters --check,
|
|
3033
|
+
// generate-platform-manifest --check, and the release-docs-self-bootstrap
|
|
3034
|
+
// single-file test) to prepare's earliest
|
|
3029
3035
|
// stage so the same drift fails closed in seconds, before any hook. Like
|
|
3030
3036
|
// the bundle freshness gate this is artifact-integrity class: workflow
|
|
3031
3037
|
// trimming never exempts it. Installed layouts record not-applicable.
|
|
@@ -3033,7 +3039,8 @@ export async function prepareRelease(options) {
|
|
|
3033
3039
|
// the CLI, plain function calls, and the node:test harness — no
|
|
3034
3040
|
// environment variable may exempt them (a test seam is never a
|
|
3035
3041
|
// production switch). Tests that need lightweight fixtures inject the
|
|
3036
|
-
// adapterFreshnessFn/selfBootstrapFactsFn
|
|
3042
|
+
// adapterFreshnessFn/platformManifestFreshnessFn/selfBootstrapFactsFn
|
|
3043
|
+
// seams explicitly in-process.
|
|
3037
3044
|
await evidence.append({ phase: 'adapter-freshness', status: 'started' });
|
|
3038
3045
|
const adapterFreshnessFn = options.adapterFreshnessFn ?? assertAdapterFreshness;
|
|
3039
3046
|
let adapterFreshness;
|
|
@@ -3062,6 +3069,35 @@ export async function prepareRelease(options) {
|
|
|
3062
3069
|
});
|
|
3063
3070
|
}
|
|
3064
3071
|
|
|
3072
|
+
await evidence.append({ phase: 'platform-manifest-freshness', status: 'started' });
|
|
3073
|
+
const platformManifestFreshnessFn = options.platformManifestFreshnessFn
|
|
3074
|
+
?? assertPlatformManifestFreshness;
|
|
3075
|
+
let platformManifestFreshness;
|
|
3076
|
+
try {
|
|
3077
|
+
platformManifestFreshness = await platformManifestFreshnessFn(PKG_ROOT);
|
|
3078
|
+
} catch (err) {
|
|
3079
|
+
await evidence.append({
|
|
3080
|
+
phase: 'platform-manifest-freshness',
|
|
3081
|
+
status: 'blocking',
|
|
3082
|
+
reason: err.details?.reason ?? null,
|
|
3083
|
+
error: { code: err.code, message: err.message },
|
|
3084
|
+
});
|
|
3085
|
+
throw err;
|
|
3086
|
+
}
|
|
3087
|
+
if (platformManifestFreshness?.applicable === false) {
|
|
3088
|
+
await evidence.append({
|
|
3089
|
+
phase: 'platform-manifest-freshness',
|
|
3090
|
+
status: 'not-applicable',
|
|
3091
|
+
reason: platformManifestFreshness.reason,
|
|
3092
|
+
});
|
|
3093
|
+
} else {
|
|
3094
|
+
await evidence.append({
|
|
3095
|
+
phase: 'platform-manifest-freshness',
|
|
3096
|
+
status: 'completed',
|
|
3097
|
+
durationMs: platformManifestFreshness?.durationMs ?? null,
|
|
3098
|
+
});
|
|
3099
|
+
}
|
|
3100
|
+
|
|
3065
3101
|
await evidence.append({ phase: 'self-bootstrap-facts', status: 'started' });
|
|
3066
3102
|
const selfBootstrapFactsFn = options.selfBootstrapFactsFn ?? assertSelfBootstrapFacts;
|
|
3067
3103
|
let selfBootstrapFacts;
|
|
@@ -3092,38 +3128,31 @@ export async function prepareRelease(options) {
|
|
|
3092
3128
|
|
|
3093
3129
|
// --- Step 1c: postPublish distribution declaration gate (R1/R2) ---
|
|
3094
3130
|
// The per-unit postPublish block drives the post-publish distribute
|
|
3095
|
-
// command. Validate
|
|
3096
|
-
// check, or plan write, so an unsafe declaration fails
|
|
3097
|
-
// side effects. This is the runtime re-check on top of
|
|
3098
|
-
// schema: plans frozen by older schema versions must not
|
|
3099
|
-
// smuggle shell strings, option-like executables, or
|
|
3100
|
-
// keys through. R2: preset references resolve against the
|
|
3101
|
-
// preset registry (per-preset config validation +
|
|
3102
|
-
// grading), and targets normalize onto preset hooks —
|
|
3103
|
-
// table is a deterministic projection of the digest-bound
|
|
3104
|
-
// so any list change changes the plan digest and voids
|
|
3105
|
-
//
|
|
3106
|
-
//
|
|
3131
|
+
// command. Validate every declaration here, before any hook, baseline,
|
|
3132
|
+
// snapshot, remote check, or plan write, so an unsafe declaration fails
|
|
3133
|
+
// closed with zero side effects. This is the runtime re-check on top of
|
|
3134
|
+
// the config JSON schema: plans frozen by older schema versions must not
|
|
3135
|
+
// be able to smuggle shell strings, option-like executables, or
|
|
3136
|
+
// secret-ish env keys through. R2: preset references resolve against the
|
|
3137
|
+
// built-in preset registry (per-preset config validation +
|
|
3138
|
+
// requiresApproval grading), and targets normalize onto preset hooks —
|
|
3139
|
+
// the normalized table is a deterministic projection of the digest-bound
|
|
3140
|
+
// declaration, so any list change changes the plan digest and voids
|
|
3141
|
+
// approvals. Multiple units may each declare postPublish (multi-release-
|
|
3142
|
+
// unit v3): every declaration is validated individually, in plan.units
|
|
3143
|
+
// order, and the whole project's EXPLICIT hooks[].id must be unique
|
|
3144
|
+
// across units (target and internal probe local ids may repeat; the
|
|
3145
|
+
// (planDigest, hookId) approval contract needs globally unique hook ids).
|
|
3107
3146
|
const postPublishDeclarations = configUnits
|
|
3108
3147
|
.map((unit, index) => ({ unit, index }))
|
|
3109
3148
|
.filter(({ unit }) => unit.postPublish !== undefined);
|
|
3110
|
-
|
|
3111
|
-
throw new ReleaseError(
|
|
3112
|
-
GATE_FAILED,
|
|
3113
|
-
`multiple units declare postPublish (${postPublishDeclarations.map(({ unit }) => unit.id).join(', ')}); a release plan binds exactly one postPublish declaration`,
|
|
3114
|
-
{ unitIds: postPublishDeclarations.map(({ unit }) => unit.id) },
|
|
3115
|
-
);
|
|
3116
|
-
}
|
|
3117
|
-
let postPublishDeclaration = null;
|
|
3118
|
-
if (postPublishDeclarations.length === 1) {
|
|
3119
|
-
const { unit, index } = postPublishDeclarations[0];
|
|
3149
|
+
for (const { unit } of postPublishDeclarations) {
|
|
3120
3150
|
validatePostPublishDeclaration(unit.postPublish, { unitId: unit.id });
|
|
3121
3151
|
// Normalized hook table (design §2.2): validate the dependency
|
|
3122
3152
|
// topology at freeze time too, so a cyclic/dangling declaration can
|
|
3123
3153
|
// never be frozen for distribute to trip over.
|
|
3124
3154
|
const normalizedDeclaration = normalizePostPublishDeclaration(unit.postPublish);
|
|
3125
3155
|
const orderedNormalizedHooks = orderNormalizedHooks(normalizedDeclaration.hooks);
|
|
3126
|
-
postPublishDeclaration = { unit, index };
|
|
3127
3156
|
await evidence.append({
|
|
3128
3157
|
phase: 'postpublish-declaration',
|
|
3129
3158
|
status: 'validated',
|
|
@@ -3134,6 +3163,19 @@ export async function prepareRelease(options) {
|
|
|
3134
3163
|
preGates: normalizedDeclaration.preGates.map((gate) => gate.gate),
|
|
3135
3164
|
});
|
|
3136
3165
|
}
|
|
3166
|
+
// Whole-project explicit hooks[].id uniqueness (design §9.2 rule 3;
|
|
3167
|
+
// rework R-02): the (planDigest, hookId) approval contract binds every
|
|
3168
|
+
// explicit hook id plan-wide, so a duplicate across units must fail
|
|
3169
|
+
// before any plan write and before any project hook runs. The single
|
|
3170
|
+
// array-level authority lives in core/postpublish.mjs
|
|
3171
|
+
// validatePostPublishHookIdUniqueness — this entry only normalizes its
|
|
3172
|
+
// input to the declaration array view (config blocks carry no unitId;
|
|
3173
|
+
// the owning unit id is bound here, exactly as the frozen plan does) and
|
|
3174
|
+
// calls it. Per-declaration duplicates are already rejected by
|
|
3175
|
+
// validatePostPublishDeclaration.
|
|
3176
|
+
validatePostPublishHookIdUniqueness(
|
|
3177
|
+
postPublishDeclarations.map(({ unit }) => ({ ...unit.postPublish, unitId: unit.id })),
|
|
3178
|
+
);
|
|
3137
3179
|
|
|
3138
3180
|
// --- Step 2: Hook authorization gate ---
|
|
3139
3181
|
// Hooks are user-configured arbitrary local processes without filesystem
|
|
@@ -3841,20 +3883,21 @@ export async function prepareRelease(options) {
|
|
|
3841
3883
|
});
|
|
3842
3884
|
}
|
|
3843
3885
|
|
|
3844
|
-
// New prepares emit planVersion
|
|
3845
|
-
//
|
|
3846
|
-
//
|
|
3847
|
-
//
|
|
3848
|
-
//
|
|
3849
|
-
// every unit's
|
|
3850
|
-
//
|
|
3851
|
-
//
|
|
3852
|
-
//
|
|
3853
|
-
//
|
|
3854
|
-
//
|
|
3855
|
-
//
|
|
3856
|
-
// the
|
|
3857
|
-
//
|
|
3886
|
+
// New prepares emit planVersion 3 (multi-release-unit postPublish v3;
|
|
3887
|
+
// v3 inherits the v2 record-layer freeze-timestamp semantics from
|
|
3888
|
+
// t1-2-digest-decoupling.md §4.2/§7). Production freeze timestamps are
|
|
3889
|
+
// derived deterministically from the baseline headCommit's committer
|
|
3890
|
+
// date, before the first frozen Git object exists. This single canonical
|
|
3891
|
+
// value becomes GIT_AUTHOR_DATE/GIT_COMMITTER_DATE for every unit's
|
|
3892
|
+
// frozen commit and every unit's frozenSnapshot.commitTimestamp;
|
|
3893
|
+
// identical sources freeze byte-identical Git objects on every
|
|
3894
|
+
// re-prepare. The wall-clock sample is still validated here (fail closed
|
|
3895
|
+
// before any Git write) and becomes plan.createdAt -- record-layer real
|
|
3896
|
+
// clock behind the 24h approval window, no longer equal to the freeze
|
|
3897
|
+
// timestamp for v2/v3 plans. The v1 legacy path used this same sample as
|
|
3898
|
+
// the freeze timestamp itself (commitTimestamp == createdAt). publish,
|
|
3899
|
+
// retry, and reconcile consume the frozen value from the plan and never
|
|
3900
|
+
// re-read the wall clock or re-derive it.
|
|
3858
3901
|
const createdAtTimestamp = production
|
|
3859
3902
|
? normalizeGitTimestamp(clock ? clock() : new Date().toISOString(), 'plan createdAt timestamp')
|
|
3860
3903
|
: null;
|
|
@@ -4252,16 +4295,20 @@ export async function prepareRelease(options) {
|
|
|
4252
4295
|
? 'foundationPayloadThenManualFollowUps'
|
|
4253
4296
|
: 'manualFollowUps';
|
|
4254
4297
|
|
|
4255
|
-
// --- Fold the validated postPublish
|
|
4256
|
-
//
|
|
4257
|
-
//
|
|
4258
|
-
//
|
|
4298
|
+
// --- Fold the validated postPublish declarations into the frozen plan ---
|
|
4299
|
+
// Multi-release-unit v3 contract: the frozen field is ALWAYS an array,
|
|
4300
|
+
// one entry per declaring unit in plan.units order (zero declarations
|
|
4301
|
+
// freeze the empty array, which never enters post-release execution).
|
|
4302
|
+
// Bindings per entry: tag (tagTemplate rendered at the resolved target
|
|
4303
|
+
// version — the same computation create-tag will use), tagCommit (the
|
|
4304
|
+
// frozen production asset commit the tag will point at; only production
|
|
4259
4305
|
// prepares can freeze it — distribute fails closed when it is absent),
|
|
4260
4306
|
// unitId (declaring unit), and payloadSource "tag-worktree" (R1 timing
|
|
4261
4307
|
// contract: payload may only come from the detached worktree at
|
|
4262
|
-
// tagCommit, never from workspace state).
|
|
4263
|
-
//
|
|
4264
|
-
//
|
|
4308
|
+
// tagCommit, never from workspace state). The array and every entry
|
|
4309
|
+
// participate in the plan digest through the existing digest mechanism
|
|
4310
|
+
// (no per-entry summaries, maps, or second manifest), so declaration
|
|
4311
|
+
// order and any bound field change change the plan digest.
|
|
4265
4312
|
//
|
|
4266
4313
|
// F-01 / T1 private execution bundle: parent-workspace files that the
|
|
4267
4314
|
// post-publish commands need but the frozen tag does not contain are
|
|
@@ -4271,9 +4318,8 @@ export async function prepareRelease(options) {
|
|
|
4271
4318
|
// plan is the bundle's only source of truth (no parallel manifest, no
|
|
4272
4319
|
// second bundle digest): the raw executionFiles list folds into the
|
|
4273
4320
|
// closure and is NOT duplicated into the frozen block.
|
|
4274
|
-
|
|
4275
|
-
|
|
4276
|
-
const { unit, index } = postPublishDeclaration;
|
|
4321
|
+
const frozenPostPublish = [];
|
|
4322
|
+
for (const { unit, index } of postPublishDeclarations) {
|
|
4277
4323
|
const { tag } = resolveProductionBranch(unit, resolvedVersions[index]);
|
|
4278
4324
|
const declaredExecutionFiles = unit.postPublish.executionFiles ?? [];
|
|
4279
4325
|
const frozenTagPaths = productionAssets
|
|
@@ -4300,18 +4346,22 @@ export async function prepareRelease(options) {
|
|
|
4300
4346
|
bundleRoot: relative(realRoot, bundleRootForAuthorityDir(releaseDir)),
|
|
4301
4347
|
});
|
|
4302
4348
|
const { executionFiles: _executionFiles, ...declarationWithoutManifest } = structuredClone(unit.postPublish);
|
|
4303
|
-
frozenPostPublish
|
|
4349
|
+
frozenPostPublish.push({
|
|
4304
4350
|
...declarationWithoutManifest,
|
|
4305
4351
|
tag,
|
|
4306
4352
|
...(productionAssets ? { tagCommit: productionAssets[index].commit } : {}),
|
|
4307
4353
|
unitId: unit.id,
|
|
4308
4354
|
payloadSource: PAYLOAD_SOURCE_TAG_WORKTREE,
|
|
4309
4355
|
executionBundle,
|
|
4310
|
-
};
|
|
4356
|
+
});
|
|
4311
4357
|
}
|
|
4312
4358
|
|
|
4313
4359
|
const plan = {
|
|
4314
|
-
planVersion:
|
|
4360
|
+
// New prepares emit planVersion 3 (multi-release-unit postPublish v3):
|
|
4361
|
+
// postPublish is the declaration ARRAY (possibly empty). v3 inherits
|
|
4362
|
+
// the v2 record-layer digest and approval semantics; only the
|
|
4363
|
+
// postPublish shape changes.
|
|
4364
|
+
planVersion: 3,
|
|
4315
4365
|
status: 'PREPARED',
|
|
4316
4366
|
// Workflow profile (H5): 'full' for the complete gate set;
|
|
4317
4367
|
// 'docs'/'config'/'marketplace' for trimmed code-class gates. Both
|
|
@@ -4352,7 +4402,7 @@ export async function prepareRelease(options) {
|
|
|
4352
4402
|
units,
|
|
4353
4403
|
externalActions,
|
|
4354
4404
|
...(publicSourceAuthorityReceipt ? { publicSourceAuthorityReceipt } : {}),
|
|
4355
|
-
|
|
4405
|
+
postPublish: frozenPostPublish,
|
|
4356
4406
|
...(sourceAuthority ? { sourceAuthority } : {}),
|
|
4357
4407
|
createdAt: production ? createdAtTimestamp : (clock ? clock() : new Date().toISOString()),
|
|
4358
4408
|
};
|
package/src/commands/publish.mjs
CHANGED
|
@@ -791,21 +791,22 @@ export async function publishRelease(options) {
|
|
|
791
791
|
|
|
792
792
|
const currentBaseline = await captureBaselineActual(root);
|
|
793
793
|
|
|
794
|
-
// planVersion fork (design: t1-2-digest-decoupling.md §4.3): for
|
|
795
|
-
//
|
|
796
|
-
//
|
|
797
|
-
//
|
|
798
|
-
//
|
|
799
|
-
|
|
794
|
+
// planVersion fork (design: t1-2-digest-decoupling.md §4.3): for
|
|
795
|
+
// planVersion 2/3 plans (v3 inherits the v2 semantics per 多发布单元
|
|
796
|
+
// postPublish v3 §4.2) the baseline is record-layer audit data. Drift is
|
|
797
|
+
// recorded as a warning and execution continues -- artifact integrity is
|
|
798
|
+
// sealed by the frozen-artifact re-verification above, never by workspace
|
|
799
|
+
// equality. v1 plans keep the BASELINE_CHANGED hard failure, byte for byte.
|
|
800
|
+
const recordLayerBaseline = plan.planVersion === 2 || plan.planVersion === 3;
|
|
800
801
|
|
|
801
802
|
if (currentBaseline.gitTreeHash !== plan.baseline.gitTreeHash) {
|
|
802
|
-
if (
|
|
803
|
+
if (recordLayerBaseline) {
|
|
803
804
|
await evidence.append({
|
|
804
805
|
phase: 'safety-gate',
|
|
805
806
|
gate: 'baseline-check',
|
|
806
807
|
status: 'warning',
|
|
807
808
|
severity: 'warning',
|
|
808
|
-
reason: 'planVersion 2: baseline drift is record-layer audit data; frozen-artifact re-verification remains the integrity authority',
|
|
809
|
+
reason: 'planVersion 2/3: baseline drift is record-layer audit data; frozen-artifact re-verification remains the integrity authority',
|
|
809
810
|
planTreeHash: plan.baseline.gitTreeHash,
|
|
810
811
|
currentTreeHash: currentBaseline.gitTreeHash,
|
|
811
812
|
});
|
|
@@ -831,13 +832,13 @@ export async function publishRelease(options) {
|
|
|
831
832
|
plan.baseline.workspaceDigest &&
|
|
832
833
|
currentBaseline.workspaceDigest !== plan.baseline.workspaceDigest
|
|
833
834
|
) {
|
|
834
|
-
if (
|
|
835
|
+
if (recordLayerBaseline) {
|
|
835
836
|
await evidence.append({
|
|
836
837
|
phase: 'safety-gate',
|
|
837
838
|
gate: 'baseline-check',
|
|
838
839
|
status: 'warning',
|
|
839
840
|
severity: 'warning',
|
|
840
|
-
reason: 'planVersion 2: workspace digest drift is record-layer audit data; frozen-artifact re-verification remains the integrity authority',
|
|
841
|
+
reason: 'planVersion 2/3: workspace digest drift is record-layer audit data; frozen-artifact re-verification remains the integrity authority',
|
|
841
842
|
planWorkspaceDigest: plan.baseline.workspaceDigest,
|
|
842
843
|
currentWorkspaceDigest: currentBaseline.workspaceDigest,
|
|
843
844
|
});
|
|
@@ -357,21 +357,22 @@ export async function reconcileRelease(options) {
|
|
|
357
357
|
|
|
358
358
|
const currentBaseline = await captureBaselineActual(root);
|
|
359
359
|
|
|
360
|
-
// planVersion fork (design: t1-2-digest-decoupling.md §4.3): for
|
|
361
|
-
//
|
|
362
|
-
//
|
|
363
|
-
//
|
|
364
|
-
//
|
|
365
|
-
|
|
360
|
+
// planVersion fork (design: t1-2-digest-decoupling.md §4.3): for
|
|
361
|
+
// planVersion 2/3 plans (v3 inherits the v2 semantics per 多发布单元
|
|
362
|
+
// postPublish v3 §4.2) the baseline is record-layer audit data. Drift is
|
|
363
|
+
// recorded as a warning and execution continues -- artifact integrity is
|
|
364
|
+
// sealed by the frozen-artifact re-verification, never by workspace
|
|
365
|
+
// equality. v1 plans keep the BASELINE_CHANGED hard failure, byte for byte.
|
|
366
|
+
const recordLayerBaseline = plan.planVersion === 2 || plan.planVersion === 3;
|
|
366
367
|
|
|
367
368
|
if (currentBaseline.gitTreeHash !== plan.baseline.gitTreeHash) {
|
|
368
|
-
if (
|
|
369
|
+
if (recordLayerBaseline) {
|
|
369
370
|
await evidence.append({
|
|
370
371
|
phase: 'safety-gate',
|
|
371
372
|
gate: 'baseline-check',
|
|
372
373
|
status: 'warning',
|
|
373
374
|
severity: 'warning',
|
|
374
|
-
reason: 'planVersion 2: baseline drift is record-layer audit data; frozen-artifact re-verification remains the integrity authority',
|
|
375
|
+
reason: 'planVersion 2/3: baseline drift is record-layer audit data; frozen-artifact re-verification remains the integrity authority',
|
|
375
376
|
planTreeHash: plan.baseline.gitTreeHash,
|
|
376
377
|
currentTreeHash: currentBaseline.gitTreeHash,
|
|
377
378
|
});
|
|
@@ -396,13 +397,13 @@ export async function reconcileRelease(options) {
|
|
|
396
397
|
plan.baseline.workspaceDigest &&
|
|
397
398
|
currentBaseline.workspaceDigest !== plan.baseline.workspaceDigest
|
|
398
399
|
) {
|
|
399
|
-
if (
|
|
400
|
+
if (recordLayerBaseline) {
|
|
400
401
|
await evidence.append({
|
|
401
402
|
phase: 'safety-gate',
|
|
402
403
|
gate: 'baseline-check',
|
|
403
404
|
status: 'warning',
|
|
404
405
|
severity: 'warning',
|
|
405
|
-
reason: 'planVersion 2: workspace digest drift is record-layer audit data; frozen-artifact re-verification remains the integrity authority',
|
|
406
|
+
reason: 'planVersion 2/3: workspace digest drift is record-layer audit data; frozen-artifact re-verification remains the integrity authority',
|
|
406
407
|
planWorkspaceDigest: plan.baseline.workspaceDigest,
|
|
407
408
|
currentWorkspaceDigest: currentBaseline.workspaceDigest,
|
|
408
409
|
});
|
package/src/commands/setup.mjs
CHANGED
|
@@ -34,7 +34,7 @@ import {
|
|
|
34
34
|
} from '../core/errors.mjs';
|
|
35
35
|
import { readTrustedPackageResource } from '../core/trusted-resource.mjs';
|
|
36
36
|
import { validatePresetHook } from '../core/presets.mjs';
|
|
37
|
-
import { validatePostPublishDeclaration } from '../core/postpublish.mjs';
|
|
37
|
+
import { validatePostPublishDeclaration, validatePostPublishHookIdUniqueness } from '../core/postpublish.mjs';
|
|
38
38
|
import { loadProjectConfig } from '../core/config.mjs';
|
|
39
39
|
import { resolveProducerVersion } from '../core/evidence.mjs';
|
|
40
40
|
import { validateHook } from '../core/hooks.mjs';
|
|
@@ -46,7 +46,6 @@ import {
|
|
|
46
46
|
deriveGateSuggestions,
|
|
47
47
|
deriveHookDurations,
|
|
48
48
|
deriveLongHookSuggestions,
|
|
49
|
-
findPostPublishUnitConflict,
|
|
50
49
|
scanGateDeclarationFindings,
|
|
51
50
|
FINDING_CATEGORY,
|
|
52
51
|
ASSESSMENT_STATUS,
|
|
@@ -3101,17 +3100,31 @@ export async function assessAdoption({ root } = {}) {
|
|
|
3101
3100
|
evidence: { configDigest },
|
|
3102
3101
|
}));
|
|
3103
3102
|
|
|
3104
|
-
//
|
|
3105
|
-
|
|
3106
|
-
|
|
3107
|
-
|
|
3108
|
-
|
|
3109
|
-
|
|
3110
|
-
|
|
3111
|
-
|
|
3112
|
-
|
|
3113
|
-
|
|
3114
|
-
}));
|
|
3103
|
+
// 多发布单元 postPublish v3(设计 §9.2/§9.7;返工 R-02):多个发布单元
|
|
3104
|
+
// 各自声明 postPublish 本身不再构成缺口——每个声明的字段级校验已在上面
|
|
3105
|
+
// 逐项完成。仍失败关闭的只有:声明内容无效(POSTPUBLISH_INVALID,逐项
|
|
3106
|
+
// 报告)与整份计划显式 hooks[].id 跨单元重复((planDigest, hookId) 批准
|
|
3107
|
+
// 合同要求全局唯一;target 与内部 probe 的本地 ID 可跨单元重复,检查点
|
|
3108
|
+
// 以 unitId 区分)。唯一校验真源是 core/postpublish.mjs 的
|
|
3109
|
+
// validatePostPublishHookIdUniqueness——本入口只做输入归一(收集各单元的
|
|
3110
|
+
// 声明块)并调用同一函数,再把领域事实转成评估 finding。
|
|
3111
|
+
const declaredPostPublishBlocks = declaredUnits
|
|
3112
|
+
.filter((unit) => unit.postPublish !== undefined)
|
|
3113
|
+
.map((unit) => ({ ...unit.postPublish, unitId: unit.id }));
|
|
3114
|
+
try {
|
|
3115
|
+
validatePostPublishHookIdUniqueness(declaredPostPublishBlocks);
|
|
3116
|
+
} catch (error) {
|
|
3117
|
+
if (error?.details?.hookId !== undefined && Array.isArray(error?.details?.unitIds)) {
|
|
3118
|
+
const [owner, unitId] = error.details.unitIds;
|
|
3119
|
+
findings.push(createFinding({
|
|
3120
|
+
category: FINDING_CATEGORY.MANDATORY_GAP, code: 'POSTPUBLISH_HOOK_ID_DUPLICATE',
|
|
3121
|
+
unitId, fieldPath: 'releaseUnits[].postPublish.hooks[].id',
|
|
3122
|
+
message: `postPublish 显式 hook id "${error.details.hookId}" 同时由发布单元 "${owner}" 与 "${unitId}" 声明;整份计划的显式 hooks[].id 必须唯一(target 与内部 probe 的本地 ID 可跨单元重复)。`,
|
|
3123
|
+
evidence: { hookId: error.details.hookId, unitIds: error.details.unitIds },
|
|
3124
|
+
action: '为重复的显式 hook id 改名(或合并声明)后重新评估。',
|
|
3125
|
+
severity: 'blocking',
|
|
3126
|
+
}));
|
|
3127
|
+
}
|
|
3115
3128
|
}
|
|
3116
3129
|
|
|
3117
3130
|
// 裁决 20: 评估复用实际 Hook 校验(core/hooks.mjs 的纯校验函数,不调用
|