@timurproko/a1 0.2.3 → 0.2.4-dev.664
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/README.md +8 -0
- package/dist/foundation/release/update-recovery.d.ts +2 -0
- package/dist/foundation/release/update-recovery.js +15 -2
- package/dist/foundation/release/update.d.ts +4 -1
- package/dist/foundation/release/update.js +20 -2
- package/dist/native/darwin-arm64/manifest.json +1 -1
- package/dist/native/linux-x64/manifest.json +1 -1
- package/dist/native/win32-x64/manifest.json +2 -2
- package/dist/native/win32-x64/process-guardian.exe +0 -0
- package/docs/architecture/internal-naming.md +1 -1
- package/docs/ci-release-runbook.md +9 -3
- package/docs/releases/0.2.3.md +1 -7
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -28,6 +28,14 @@ a1 update --develop 0.1.8-dev.107 # install that exact preview
|
|
|
28
28
|
a1 update --models # refresh A1's model catalogs
|
|
29
29
|
```
|
|
30
30
|
|
|
31
|
+
If `a1 update` from `0.2.2` reports that it could not resolve npm's JavaScript entry, use the official installer once:
|
|
32
|
+
|
|
33
|
+
```sh
|
|
34
|
+
npm x -y -- @timurproko/a1-install
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
It verifies the existing installation and delegates replacement to A1's cancellation-safe updater with npm's canonical execution context. Do not delete A1 data or overwrite the package with a raw global install. After the bridge, use ordinary `a1 update` commands again.
|
|
38
|
+
|
|
31
39
|
On interactive startup A1 checks, at most once a day, whether a newer release exists on its own channel and shows an `Update Available` notice with the command to run. It never installs anything. Turn it off with the `Update check` setting (Generic section), or set `A1_SKIP_VERSION_CHECK=1`; it is also skipped when `PI_OFFLINE` or `CI` is set, or when output is not a terminal. The check reads the public npm registry, or `npm_config_registry` when set.
|
|
32
40
|
|
|
33
41
|
## Extensions
|
|
@@ -41,6 +41,8 @@ export interface ProtectedPackageReplacementOptions {
|
|
|
41
41
|
readonly globalRoot: string;
|
|
42
42
|
/** Root used only to locate the active npm implementation; installation remains pinned to globalRoot. */
|
|
43
43
|
readonly npmCliRoot?: string;
|
|
44
|
+
/** Canonical entry established before update lifecycle mutation and revalidated when the capsule is committed. */
|
|
45
|
+
readonly npmCli?: string;
|
|
44
46
|
readonly packageRoot: string;
|
|
45
47
|
readonly transaction: UpdateTransaction;
|
|
46
48
|
readonly priorRelease: {
|
|
@@ -119,7 +119,9 @@ export async function prepareUpdateRecoveryCapsule(options) {
|
|
|
119
119
|
const prefix = npmPrefixForGlobalRoot(canonicalGlobal, platform);
|
|
120
120
|
const launcherRoot = platform === "win32" ? prefix : resolve(prefix, "bin");
|
|
121
121
|
const npmCliRoot = await realpath(options.npmCliRoot ?? canonicalGlobal);
|
|
122
|
-
const npmCli =
|
|
122
|
+
const npmCli = options.npmCli === undefined
|
|
123
|
+
? await resolveNpmCli(npmCliRoot, options.environment ?? process.env, platform)
|
|
124
|
+
: await requireCanonicalNpmCli(options.npmCli);
|
|
123
125
|
const capsule = {
|
|
124
126
|
schema: UPDATE_RECOVERY_SCHEMA,
|
|
125
127
|
launchContract: PRIVATE_LAUNCH_CONTRACT,
|
|
@@ -300,7 +302,10 @@ async function spawnRecoveryWorker(entry, manifestPath, environment) {
|
|
|
300
302
|
child.unref();
|
|
301
303
|
}
|
|
302
304
|
export async function resolveNpmCli(globalRoot, environment, platform = process.platform, nodeExecutable = process.execPath) {
|
|
303
|
-
const
|
|
305
|
+
const declaredNpmEntry = platform === "win32"
|
|
306
|
+
? windowsEnvironmentValue(environment, "npm_execpath")
|
|
307
|
+
: environment.npm_execpath;
|
|
308
|
+
const candidates = [declaredNpmEntry, resolve(globalRoot, "npm", "bin", "npm-cli.js")]
|
|
304
309
|
.filter((value) => typeof value === "string" && value.length > 0);
|
|
305
310
|
for (const candidate of candidates) {
|
|
306
311
|
const canonical = await canonicalRegularFile(candidate);
|
|
@@ -342,12 +347,20 @@ async function windowsNpmCliFromPath(environment) {
|
|
|
342
347
|
return null;
|
|
343
348
|
}
|
|
344
349
|
function windowsEnvironmentValue(environment, name) {
|
|
350
|
+
if (Object.hasOwn(environment, name))
|
|
351
|
+
return environment[name];
|
|
345
352
|
const key = Object.keys(environment).find(candidate => candidate.toLowerCase() === name.toLowerCase());
|
|
346
353
|
return key ? environment[key] : undefined;
|
|
347
354
|
}
|
|
348
355
|
function unquoteWindowsPathEntry(value) {
|
|
349
356
|
return value.length >= 2 && value.startsWith('"') && value.endsWith('"') ? value.slice(1, -1) : value;
|
|
350
357
|
}
|
|
358
|
+
async function requireCanonicalNpmCli(path) {
|
|
359
|
+
const canonical = await canonicalRegularFile(path);
|
|
360
|
+
if (!canonical)
|
|
361
|
+
throw new Error("preflight npm JavaScript entry is no longer a regular file");
|
|
362
|
+
return canonical;
|
|
363
|
+
}
|
|
351
364
|
async function canonicalRegularFile(path) {
|
|
352
365
|
const canonical = await realpath(path).catch(() => null);
|
|
353
366
|
if (!canonical)
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { UpdateChannel } from "./types.js";
|
|
2
2
|
import { type UpdateActivationPhase, type UpdateMaterializationProgress } from "./update-activation.js";
|
|
3
3
|
import { type UpdateRecoveryState, type UpdateTransaction, type UpdateTransactionPhase } from "./update-transaction.js";
|
|
4
|
-
import { type ProtectedPackageReplacementResult } from "./update-recovery.js";
|
|
4
|
+
import { resolveNpmCli, type ProtectedPackageReplacementResult } from "./update-recovery.js";
|
|
5
5
|
export declare const PRODUCT_PACKAGE: string;
|
|
6
6
|
export type { UpdateChannel } from "./types.js";
|
|
7
7
|
export interface ProcessRequest {
|
|
@@ -69,6 +69,8 @@ export interface SelfUpdateOptions {
|
|
|
69
69
|
transactionStore?: UpdateTransactionJournal;
|
|
70
70
|
/** Test seam for protected global package replacement. */
|
|
71
71
|
packageReplacement?: (input: UpdatePackageReplacementInput) => Promise<ProtectedPackageReplacementResult>;
|
|
72
|
+
/** Test seam for proving replacement-authority preflight ordering. */
|
|
73
|
+
npmCliResolver?: typeof resolveNpmCli;
|
|
72
74
|
/** Test or embedding seam for post-activation release maintenance. */
|
|
73
75
|
maintenance?: () => Promise<void>;
|
|
74
76
|
progress?: boolean;
|
|
@@ -102,6 +104,7 @@ export interface UpdatePackageReplacementInput {
|
|
|
102
104
|
readonly dataDir: string;
|
|
103
105
|
readonly globalRoot: string;
|
|
104
106
|
readonly npmCliRoot: string;
|
|
107
|
+
readonly npmCli?: string;
|
|
105
108
|
readonly packageRoot: string;
|
|
106
109
|
readonly transaction: UpdateTransaction;
|
|
107
110
|
readonly priorRelease: {
|
|
@@ -16,7 +16,7 @@ import { materializeRelease, readMaterializedRelease } from "./release-store.js"
|
|
|
16
16
|
import { scheduleReleaseCleanup } from "./release-gc.js";
|
|
17
17
|
import { UPDATE_ACTIVATION_CONTRACT, activateInstalledRelease, delegateActivation, readActivationContracts, } from "./update-activation.js";
|
|
18
18
|
import { UpdateTransactionStore } from "./update-transaction.js";
|
|
19
|
-
import { npmPrefixForGlobalRoot, removeUpdateRecoveryCapsule, runProtectedPackageReplacement, updateLauncherPaths, updateNpmInstallArguments, } from "./update-recovery.js";
|
|
19
|
+
import { npmPrefixForGlobalRoot, removeUpdateRecoveryCapsule, resolveNpmCli, runProtectedPackageReplacement, updateLauncherPaths, updateNpmInstallArguments, } from "./update-recovery.js";
|
|
20
20
|
export const PRODUCT_PACKAGE = PRODUCT_TEXT.packageName;
|
|
21
21
|
const UPDATE_DIST_TAGS = { stable: "latest", next: "next" };
|
|
22
22
|
const defaultFileSystem = {
|
|
@@ -520,6 +520,7 @@ export async function runSelfUpdate(options) {
|
|
|
520
520
|
? async () => { }
|
|
521
521
|
: async () => await scheduleReleaseCleanup(paths.dataDir, paths));
|
|
522
522
|
let transaction = await transactionStore.read();
|
|
523
|
+
let npmCli;
|
|
523
524
|
try {
|
|
524
525
|
if ((!transaction || transaction.status === "completed") && await lifecycle.targetIsActive(targetVersion)) {
|
|
525
526
|
await maintenance();
|
|
@@ -527,7 +528,17 @@ export async function runSelfUpdate(options) {
|
|
|
527
528
|
output.stdout(`${PRODUCT_TEXT.commandName} is up to date — no update needed.\n`);
|
|
528
529
|
return 0;
|
|
529
530
|
}
|
|
530
|
-
|
|
531
|
+
const preflightRequired = options.npmCliResolver !== undefined
|
|
532
|
+
|| options.packageReplacement === undefined && options.runner === undefined;
|
|
533
|
+
if (preflightRequired) {
|
|
534
|
+
try {
|
|
535
|
+
npmCli = await (options.npmCliResolver ?? resolveNpmCli)(npmCliRoot, environment);
|
|
536
|
+
}
|
|
537
|
+
catch (error) {
|
|
538
|
+
throw new UpdatePreflightFailure(errorMessage(error));
|
|
539
|
+
}
|
|
540
|
+
}
|
|
541
|
+
// Rationale: the bar first appears only after every non-mutating replacement prerequisite passes.
|
|
531
542
|
progress.set(3, 15);
|
|
532
543
|
const cohortStore = new CohortStateStore(paths.dataDir);
|
|
533
544
|
let cohortState = await cohortStore.read();
|
|
@@ -573,6 +584,7 @@ export async function runSelfUpdate(options) {
|
|
|
573
584
|
dataDir: paths.dataDir,
|
|
574
585
|
globalRoot,
|
|
575
586
|
npmCliRoot,
|
|
587
|
+
...(npmCli === undefined ? {} : { npmCli }),
|
|
576
588
|
packageRoot,
|
|
577
589
|
transaction: replacementTransaction,
|
|
578
590
|
priorRelease,
|
|
@@ -655,6 +667,10 @@ export async function runSelfUpdate(options) {
|
|
|
655
667
|
catch (error) {
|
|
656
668
|
progress.clear();
|
|
657
669
|
const message = errorMessage(error);
|
|
670
|
+
if (error instanceof UpdatePreflightFailure) {
|
|
671
|
+
output.stderr(`${PRODUCT_TEXT.diagnostic(`update could not start safely: ${message}. Nothing was changed. Retry with npm x -y -- ${PRODUCT_PACKAGE}-install`)}\n`);
|
|
672
|
+
return 1;
|
|
673
|
+
}
|
|
658
674
|
const rollback = options.lifecycle
|
|
659
675
|
? "previous test lifecycle retained"
|
|
660
676
|
: await rollbackPriorCohort(paths.dataDir, environment, transaction?.priorActiveReleaseId ?? null).catch(rollbackError => `rollback failed: ${errorMessage(rollbackError)}`);
|
|
@@ -760,6 +776,8 @@ class UpdateFailure extends Error {
|
|
|
760
776
|
exitCode;
|
|
761
777
|
constructor(exitCode, message) { super(message); this.exitCode = exitCode; }
|
|
762
778
|
}
|
|
779
|
+
class UpdatePreflightFailure extends Error {
|
|
780
|
+
}
|
|
763
781
|
async function canonicalImmutableRoot(dataDir, releaseRoot) {
|
|
764
782
|
try {
|
|
765
783
|
const [store, selected] = await Promise.all([realpath(resolve(dataDir, "releases")), realpath(releaseRoot)]);
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
"platform": "darwin",
|
|
6
6
|
"architecture": "arm64",
|
|
7
7
|
"capability": "supported",
|
|
8
|
-
"builtAt": "2026-10-
|
|
8
|
+
"builtAt": "2026-10-03T10:56:57.116Z",
|
|
9
9
|
"artifact": {
|
|
10
10
|
"filename": "process-guardian",
|
|
11
11
|
"sha256": "d3979187180449cb181eb92c187d1c8d0bfc4b2ba442e2e0a7c32704ea7e9589",
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
"platform": "linux",
|
|
6
6
|
"architecture": "x64",
|
|
7
7
|
"capability": "supported",
|
|
8
|
-
"builtAt": "2026-10-
|
|
8
|
+
"builtAt": "2026-10-03T10:56:40.417Z",
|
|
9
9
|
"artifact": {
|
|
10
10
|
"filename": "process-guardian",
|
|
11
11
|
"sha256": "a316d11a4aabe5aa88a360169206a87a11fe4b355e836b77cd8a7950279ab415",
|
|
@@ -5,10 +5,10 @@
|
|
|
5
5
|
"platform": "win32",
|
|
6
6
|
"architecture": "x64",
|
|
7
7
|
"capability": "supported",
|
|
8
|
-
"builtAt": "2026-10-
|
|
8
|
+
"builtAt": "2026-10-03T10:57:08.998Z",
|
|
9
9
|
"artifact": {
|
|
10
10
|
"filename": "process-guardian.exe",
|
|
11
|
-
"sha256": "
|
|
11
|
+
"sha256": "121e6f570287541ace2c806411da2ca836cb2acab9b1205054b0d68b809d363d",
|
|
12
12
|
"size": 177152
|
|
13
13
|
},
|
|
14
14
|
"provenance": {
|
|
Binary file
|
|
@@ -80,7 +80,7 @@ Metadata that predates contract declaration is stale, never fatal. A pre-cutover
|
|
|
80
80
|
|
|
81
81
|
Activation after the global installation is handed over the same way. The package manifest declares `updateActivationContracts` (`activate-v1`) and ships `bin/activate.js`; an updater that serves a declared contract starts that entry with the data directory and the target version and relays its line-delimited progress events (`materializing`, `phase`, `warmup`, `completed`, `failed`) into its own journal and progress bar, while the entry materializes, certifies, warms, and supervises the tree it ships in with that tree's own code. The updater resolves nothing inside the installed tree but `package.json` and that entry; `test/repository-governance/update-activation-contract.test.ts` pins this. A tree that declares no contract the updater serves is activated in-process with the layout it had, which keeps an updater older than the contract and a downgrade to an older preview working.
|
|
82
82
|
|
|
83
|
-
`test/foundation/release/update-predecessor.integration.test.ts` is the gate for this. It installs each of the most recent published releases and drives the candidate through **that release's own** materialization and warmup, because a fixture built from the candidate would only prove the candidate agrees with itself. No reset or migration runs automatically. If disposable state blocks installation, inspect its resolved paths and obtain separate approval before removal.
|
|
83
|
+
`test/foundation/release/update-predecessor.integration.test.ts` is the gate for this. It installs each of the most recent published releases and drives the candidate through **that release's own** materialization and warmup, because a fixture built from the candidate would only prove the candidate agrees with itself. On Windows it also drives the newest predecessor's protected package replacement from a user-style A1 prefix with npm beside a separate Node installation, then exercises the explicit `0.2.2` installer-context bridge. Candidate-only recovery success therefore cannot hide a target-side fix that its installed predecessor cannot reach. No reset or migration runs automatically. If disposable state blocks installation, inspect its resolved paths and obtain separate approval before removal.
|
|
84
84
|
|
|
85
85
|
| State | Treatment |
|
|
86
86
|
| --- | --- |
|
|
@@ -99,7 +99,7 @@ Ordinary type, architecture, unit/contract, and dist checks always run for code
|
|
|
99
99
|
|
|
100
100
|
Integration owners declare one cadence in `config/integration-owners.json`. Impact mode selects affected `pull-request` owners: a changed production path selects its coarse owner, a changed test selects its owner, and a changed file under `test/support/` or `test/fixtures/` selects the owners of the retained tests that import it directly or through other support files (the selection lists those tests as its `shared-support` reason). A support file that no retained test imports, or a test tree the scanner cannot read, falls back to every owner the shared rule declares and records `shared-support-declared`; an invalidator, unknown operational path, or manual Development dispatch selects every `pull-request` owner. An implementation-bound PR body does not force conservative selection; it only removes the documentation-only and version-only shortcuts. The modular matrix itself is derived from the selection by `scripts/release/validation-matrix.mjs`, so inactive jobs are not scheduled at all rather than checking out and exiting early. `exhaustive` owners are never silently skipped or reported as passed: impact and aggregate evidence list them as cadence-deferred, and malformed cadence blocks selection. Full regression and nightly/stable release still execute both cadence classes. PR-attached Full regression is selected only when base policy verifies an `openspec-ci` App-authored candidate, its generated repair identity, and every qualifying failed Full regression source field in `regression-provenance.json` against GitHub's read-only workflow-run record. Release/publishing, shared-support, prerequisite, build, validation-authority, unknown-path, label, Publish-only repair, and successful-run persistent-overrun changes retain bounded ordinary validation. Planning-only generated repair drafts remain exempt; eligible implementation drafts receive native lane feedback without integration authority.
|
|
101
101
|
|
|
102
|
-
The real three-release `update-predecessor` scenario is exhaustive because
|
|
102
|
+
The real three-release `update-predecessor` scenario is exhaustive because its fresh npm installations and private-prefix protected replacements dominate recent PR critical paths, and `update-performance` is exhaustive because its assertion is wall-clock timing on a shared runner. On Windows the predecessor owner requires the newest published updater to replace and activate the exact candidate with A1 under a user-style prefix and npm beside a separate Node installation; it separately proves the declared `0.2.2` official-installer context bridge without relabeling that immutable build as direct-update capable. The update path's deterministic contracts stay on pull requests through `pi-release-resume` and `package-contracts`, including preflight ordering, installer npm-context delegation, failure containment, lifecycle, recovery, materialization, warmup, package, and update behavior. This permits a real published-history incompatibility or update slowdown to reach `develop` before the next exhaustive run detects it, but nightly and stable publication remain blocked by the exhaustive predecessor outcome. `.github/workflows/full-regression.yml` runs every night at `02:47 UTC` against the `develop` tip with every owner and enforced budgets, independent of publication, so such a regression shows as a failed Full regression run the next morning; nightly publication's own complete validation still blocks publication. For diagnosis on any other candidate, dispatch standalone Full regression; labels and high-risk release/update impact do not add it to PR checks or change owner cadence.
|
|
103
103
|
|
|
104
104
|
Development outcomes report each owner/scope invocation separately while sharing authenticated build/package preparation. The aggregate reports setup, scope, job, aggregate-processing, total runner, and runner-critical-path durations. The acceptance targets are at most eight minutes of runner critical path and five minutes for one PR-required scope; an over-target result remains unmet without retries, timeout increases, workload reduction, or mutable installation caches. Hosted queue time is reported separately when available and is not counted as test execution.
|
|
105
105
|
|
|
@@ -270,8 +270,14 @@ the version. The retired `--approve` form remains a mutation-free usage error.
|
|
|
270
270
|
stops preparation), and maps every later first-parent commit through one merged
|
|
271
271
|
GitHub pull request. It generates Pi-style Markdown beginning
|
|
272
272
|
`## [version] - YYYY-MM-DD`, followed by applicable `### Breaking Changes`,
|
|
273
|
-
`### New Features`, `### Added`, `### Changed`, and `### Fixed` sections
|
|
274
|
-
|
|
273
|
+
`### New Features`, `### Added`, `### Changed`, and `### Fixed` sections. Explicit
|
|
274
|
+
breaking changes always remain visible. Ordinary non-breaking `chore` pull requests
|
|
275
|
+
and generated regression-triage repairs are omitted; a regression repair that proves
|
|
276
|
+
a user-facing defect must use its actual `fix(scope)` title before merge. Pi proposals
|
|
277
|
+
use `upgrade(pi)` and appear under Changed, while historical `chore(pi): upgrade`
|
|
278
|
+
titles remain compatible. A range containing only filtered maintenance says that it
|
|
279
|
+
has no user-facing changes. Preparation then creates one source-bound **draft GitHub
|
|
280
|
+
Release**. It dispatches
|
|
275
281
|
`release-candidate.yml`, which stamps the stable version, packs both packages with
|
|
276
282
|
the generated note, and runs the complete stable suite on Windows, Linux, and macOS
|
|
277
283
|
without publishing. A running or successful validation of the same source and
|
package/docs/releases/0.2.3.md
CHANGED
|
@@ -1,22 +1,16 @@
|
|
|
1
1
|
## [0.2.3] - 2026-10-03
|
|
2
2
|
|
|
3
|
-
### New Features
|
|
4
|
-
|
|
5
|
-
- auto-merge the verified release reopening PR ([#651](https://github.com/timurproko/a1/pull/651))
|
|
6
|
-
|
|
7
3
|
### Changed
|
|
8
4
|
|
|
9
5
|
- upgrade pinned Pi to 0.99.1 ([#642](https://github.com/timurproko/a1/pull/642))
|
|
10
|
-
- match changelog link decoration ([#649](https://github.com/timurproko/a1/pull/649))
|
|
11
6
|
- upgrade pinned Pi to 0.99.2 ([#652](https://github.com/timurproko/a1/pull/652))
|
|
7
|
+
- match changelog link decoration ([#649](https://github.com/timurproko/a1/pull/649))
|
|
12
8
|
- rename stable wrapper ([#655](https://github.com/timurproko/a1/pull/655))
|
|
13
9
|
|
|
14
10
|
### Fixed
|
|
15
11
|
|
|
16
|
-
- reuse PR full-regression selection across failed-job reruns ([#650](https://github.com/timurproko/a1/pull/650))
|
|
17
12
|
- restore documentation auto-merge ([#653](https://github.com/timurproko/a1/pull/653))
|
|
18
13
|
- allow development publication startup ([#654](https://github.com/timurproko/a1/pull/654))
|
|
19
14
|
- bound Windows backlog setup ([#656](https://github.com/timurproko/a1/pull/656))
|
|
20
15
|
- resolve bundled npm entry ([#657](https://github.com/timurproko/a1/pull/657))
|
|
21
16
|
- verify exact npm resources ([#659](https://github.com/timurproko/a1/pull/659))
|
|
22
|
-
- repair the 2026-10-02 full regression failure ([#660](https://github.com/timurproko/a1/pull/660))
|
package/package.json
CHANGED