agl 21.0.0 → 21.0.2

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.
@@ -4,7 +4,7 @@ export type TGitReversionDirection = 'undo' | 'redo';
4
4
  export type TGitReversionSideName = 'before' | 'after';
5
5
  export type TGitReversionWalPhase = 'intent' | 'preflighted' | 'applying' | 'targetVerified' | 'decision' | 'committed' | 'compensating' | 'compensated' | 'fenced';
6
6
  export type TGitReversionWorktreeState = 'intent' | 'created' | 'registered' | 'active' | 'removing' | 'retainedDirty' | 'removed' | 'fenced';
7
- export type TGitReversionFaultPoint = 'intent.before-fsync' | 'intent.after-fsync' | 'intent.before-ref-cas' | 'intent.after-ref-cas' | 'intent.before-commit-fsync' | 'intent.after-commit-fsync' | 'intent.before-remove' | 'intent.after-remove' | 'atomic.after-rename-before-directory-fsync' | 'capture.after-before-scan' | 'capture.before-finalize-record' | 'capture.after-finalize-record-fsync' | 'capture.after-staging-repository' | 'capture.after-intent-repository' | 'capture.after-state-repository' | 'capture.after-manifest-repository' | 'capture.before-store-create' | 'capture.after-store-create' | 'capture.before-generation-enrollment' | 'capture.after-generation-enrollment' | 'capture.after-publication-fsync' | 'capture.before-root-delete' | 'capture.after-root-delete' | 'capture.after-root-delete-fsync' | 'apply.after-intent' | 'apply.after-preflight' | 'apply.after-source-pack-import' | 'apply.after-source-pack-fsync' | 'apply.after-repository' | 'apply.after-target-verification' | 'apply.after-decision-repository' | 'apply.after-decision' | 'apply.after-state-repository' | 'apply.during-compensation' | 'apply.before-source-keep-remove' | 'apply.after-source-keep-quarantine' | 'apply.after-source-keep-remove' | 'apply.after-source-keep-remove-fsync' | 'worktree.after-intent' | 'worktree.after-create' | 'worktree.after-removed-record' | 'index.before-sidecar-fsync' | 'index.after-sidecar-fsync' | 'index.after-link' | 'index.after-link-fsync' | 'index.after-rename' | 'index.after-rename-fsync' | 'index.after-cleanup' | 'index.after-cleanup-fsync';
7
+ export type TGitReversionFaultPoint = 'intent.before-fsync' | 'intent.after-fsync' | 'intent.before-ref-cas' | 'intent.after-ref-cas' | 'intent.before-commit-fsync' | 'intent.after-commit-fsync' | 'intent.before-remove' | 'intent.after-remove' | 'atomic.after-rename-before-directory-fsync' | 'capture.after-before-scan' | 'capture.before-finalize-record' | 'capture.after-finalize-record-fsync' | 'capture.after-staging-repository' | 'capture.after-intent-repository' | 'capture.after-state-repository' | 'capture.after-manifest-repository' | 'capture.before-store-create' | 'capture.after-store-create' | 'capture.before-generation-enrollment' | 'capture.after-generation-enrollment' | 'capture.after-publication-fsync' | 'capture.before-root-delete' | 'capture.after-root-delete' | 'capture.after-root-delete-fsync' | 'apply.after-intent' | 'apply.after-preflight' | 'apply.after-source-pack-import' | 'apply.after-source-pack-fsync' | 'apply.after-repository' | 'apply.after-target-verification' | 'apply.after-decision-repository' | 'apply.after-decision' | 'apply.after-state-repository' | 'apply.during-compensation' | 'apply.before-source-keep-remove' | 'apply.after-source-keep-quarantine' | 'apply.after-source-keep-remove' | 'apply.after-source-keep-remove-fsync' | 'worktree.after-intent' | 'worktree.after-create' | 'worktree.after-removed-record' | 'worktree.record-read.after-open' | 'index.before-sidecar-fsync' | 'index.after-sidecar-fsync' | 'index.after-link' | 'index.after-link-fsync' | 'index.after-rename' | 'index.after-rename-fsync' | 'index.after-cleanup' | 'index.after-cleanup-fsync';
8
8
  export type TGitReversionReference = {
9
9
  schemaVersion: 1;
10
10
  captureKey: string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agl",
3
- "version": "21.0.0",
3
+ "version": "21.0.2",
4
4
  "controllerUpgradeManagementVersion": 2,
5
5
  "private": false,
6
6
  "description": "Agent Gateway Layer for OpenCode and FlexHarness sessions",
@@ -18,7 +18,7 @@
18
18
  "dependencies": {
19
19
  "@api.global/typedrequest": "8.0.2",
20
20
  "@api.global/typedrequest-interfaces": "7.1.0",
21
- "@api.global/typedserver": "11.0.0",
21
+ "@api.global/typedserver": "11.1.0",
22
22
  "@api.global/typedsocket": "8.0.0",
23
23
  "@design.estate/dees-catalog": "6.10.1",
24
24
  "@design.estate/dees-element": "2.4.0",
@@ -34,7 +34,7 @@
34
34
  "@push.rocks/smartdb": "5.2.1",
35
35
  "@push.rocks/smartfs": "1.7.0",
36
36
  "@push.rocks/smartsecret": "1.6.0",
37
- "@push.rocks/smartserve": "6.0.0",
37
+ "@push.rocks/smartserve": "6.0.1",
38
38
  "@push.rocks/smartshell": "4.1.0",
39
39
  "@simplewebauthn/browser": "13.3.0",
40
40
  "@simplewebauthn/server": "13.3.2",
@@ -3,6 +3,6 @@
3
3
  */
4
4
  export const commitinfo = {
5
5
  name: 'agl',
6
- version: '21.0.0',
6
+ version: '21.0.2',
7
7
  description: 'Agent Gateway Layer for OpenCode and FlexHarness sessions'
8
8
  }
@@ -665,6 +665,13 @@ class GitReversionInjectedFaultError extends Error {
665
665
  }
666
666
  }
667
667
 
668
+ class GitReversionConcurrentRecordReadError extends Error {
669
+ constructor(messageArg: string) {
670
+ super(messageArg);
671
+ this.name = 'GitReversionConcurrentRecordReadError';
672
+ }
673
+ }
674
+
668
675
  class GitReversionSourceKeepOwnershipError extends GitReversionFencedError {
669
676
  constructor(messageArg: string, optionsArg?: ErrorOptions) {
670
677
  super(messageArg, optionsArg);
@@ -3182,6 +3189,8 @@ export class ControllerGitReversion {
3182
3189
  descriptionArg: string,
3183
3190
  signalArg?: AbortSignal,
3184
3191
  requirePrivateModeArg = false,
3192
+ detectConcurrentReplacementArg = false,
3193
+ afterOpenArg?: () => Promise<void>,
3185
3194
  ): Promise<Buffer> {
3186
3195
  let handle: plugins.fs.promises.FileHandle;
3187
3196
  try {
@@ -3196,7 +3205,13 @@ export class ControllerGitReversion {
3196
3205
  throw new Error(`${descriptionArg} could not be opened safely.`, { cause: errorArg });
3197
3206
  }
3198
3207
  try {
3208
+ await afterOpenArg?.();
3199
3209
  const before = await handle.stat();
3210
+ if (detectConcurrentReplacementArg && before.nlink === 0) {
3211
+ throw new GitReversionConcurrentRecordReadError(
3212
+ `${descriptionArg} was replaced while it was opened.`,
3213
+ );
3214
+ }
3200
3215
  if (
3201
3216
  !before.isFile()
3202
3217
  || (requirePrivateModeArg && before.nlink !== 1)
@@ -3222,12 +3237,46 @@ export class ControllerGitReversion {
3222
3237
  if (total > maximumBytesArg) throw new Error(`${descriptionArg} exceeds its byte limit.`);
3223
3238
  }
3224
3239
  const after = await handle.stat();
3240
+ if (detectConcurrentReplacementArg && after.nlink === 0) {
3241
+ throw new GitReversionConcurrentRecordReadError(
3242
+ `${descriptionArg} was replaced while it was read.`,
3243
+ );
3244
+ }
3225
3245
  if (
3226
3246
  before.dev !== after.dev
3227
3247
  || before.ino !== after.ino
3228
3248
  || before.size !== after.size
3229
3249
  || total !== before.size
3250
+ || !after.isFile()
3251
+ || (requirePrivateModeArg && after.nlink !== 1)
3252
+ || (requirePrivateModeArg && (after.mode & 0o077) !== 0)
3253
+ || (typeof process.getuid === 'function' && after.uid !== process.getuid())
3230
3254
  ) throw new Error(`${descriptionArg} changed while it was read.`);
3255
+ if (detectConcurrentReplacementArg) {
3256
+ let pathAfter: plugins.fs.Stats;
3257
+ try {
3258
+ pathAfter = await plugins.fs.promises.lstat(pathArg);
3259
+ } catch (errorArg) {
3260
+ if ((errorArg as NodeJS.ErrnoException).code === 'ENOENT') {
3261
+ throw new GitReversionConcurrentRecordReadError(
3262
+ `${descriptionArg} was removed while it was read.`,
3263
+ );
3264
+ }
3265
+ throw errorArg;
3266
+ }
3267
+ if (
3268
+ !pathAfter.isFile()
3269
+ || pathAfter.isSymbolicLink()
3270
+ || pathAfter.nlink !== 1
3271
+ || (pathAfter.mode & 0o077) !== 0
3272
+ || (typeof process.getuid === 'function' && pathAfter.uid !== process.getuid())
3273
+ ) throw new Error(`${descriptionArg} pathname is not an exact owned regular file.`);
3274
+ if (pathAfter.dev !== after.dev || pathAfter.ino !== after.ino) {
3275
+ throw new GitReversionConcurrentRecordReadError(
3276
+ `${descriptionArg} was replaced while its pathname was revalidated.`,
3277
+ );
3278
+ }
3279
+ }
3231
3280
  return Buffer.concat(chunks, total);
3232
3281
  } finally {
3233
3282
  await handle.close();
@@ -12121,18 +12170,34 @@ export class ControllerGitReversion {
12121
12170
 
12122
12171
  private async readWorktreeRecordByRef<T>(refArg: string): Promise<{ oid: string; value: T } | undefined> {
12123
12172
  const path = this.worktreeRecordPathForRef(refArg);
12124
- let bytes: Buffer;
12125
- try {
12126
- bytes = await this.readBoundedRegularFile(
12127
- path,
12128
- this.limits.maximumMetadataRecordBytes,
12129
- 'Private worktree record',
12130
- undefined,
12131
- true,
12173
+ let bytes: Buffer | undefined;
12174
+ let concurrentReadError: GitReversionConcurrentRecordReadError | undefined;
12175
+ for (let attempt = 0; attempt < 4; attempt += 1) {
12176
+ try {
12177
+ bytes = await this.readBoundedRegularFile(
12178
+ path,
12179
+ this.limits.maximumMetadataRecordBytes,
12180
+ 'Private worktree record',
12181
+ undefined,
12182
+ true,
12183
+ true,
12184
+ () => this.fault({ point: 'worktree.record-read.after-open', ref: refArg }),
12185
+ );
12186
+ break;
12187
+ } catch (errorArg) {
12188
+ if ((errorArg as NodeJS.ErrnoException).code === 'ENOENT') return undefined;
12189
+ if (errorArg instanceof GitReversionConcurrentRecordReadError) {
12190
+ concurrentReadError = errorArg;
12191
+ continue;
12192
+ }
12193
+ throw errorArg;
12194
+ }
12195
+ }
12196
+ if (!bytes) {
12197
+ throw new GitReversionFencedError(
12198
+ 'The private worktree record changed repeatedly while it was read.',
12199
+ { cause: concurrentReadError },
12132
12200
  );
12133
- } catch (errorArg) {
12134
- if ((errorArg as NodeJS.ErrnoException).code === 'ENOENT') return undefined;
12135
- throw errorArg;
12136
12201
  }
12137
12202
  let value: unknown;
12138
12203
  try {
@@ -64,6 +64,7 @@ export type TGitReversionFaultPoint =
64
64
  | 'worktree.after-intent'
65
65
  | 'worktree.after-create'
66
66
  | 'worktree.after-removed-record'
67
+ | 'worktree.record-read.after-open'
67
68
  | 'index.before-sidecar-fsync'
68
69
  | 'index.after-sidecar-fsync'
69
70
  | 'index.after-link'
@@ -3,6 +3,6 @@
3
3
  */
4
4
  export const commitinfo = {
5
5
  name: 'agl',
6
- version: '21.0.0',
6
+ version: '21.0.2',
7
7
  description: 'Agent Gateway Layer for OpenCode and FlexHarness sessions'
8
8
  }