@foss.global/gitmanager 2.0.0 → 2.1.0

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.
@@ -67,6 +67,60 @@ export interface IGitManagerRepositoryArchive extends IGitManagerRepositorySelec
67
67
  extractedSizeBytes: number;
68
68
  entryCount: number;
69
69
  }
70
+ /**
71
+ * Asks whether `ancestorObjectId` is reachable from `descendantObjectId`
72
+ * (`git merge-base --is-ancestor`; a commit is its own ancestor). Both must be
73
+ * full lowercase object IDs of commits in the repository; revision
74
+ * expressions, abbreviations, and tag objects are refused.
75
+ */
76
+ export interface IGitManagerAncestryRequest extends IGitManagerRepositorySelector {
77
+ ancestorObjectId: string;
78
+ descendantObjectId: string;
79
+ /**
80
+ * Wall-clock work budget for the object lookup and the history walk,
81
+ * 1 to 60000 ms, default 10000. Exceeding it throws a `GitManagerError`
82
+ * with code `ancestryBudgetExceeded`.
83
+ */
84
+ timeoutMs?: number;
85
+ }
86
+ /**
87
+ * Limits for inspecting one `tar.gz` archive. Every limit is inclusive.
88
+ */
89
+ export interface IGitManagerArchiveInspectionRequest {
90
+ /** The archive bytes, or a stream of them; it is consumed and closed. */
91
+ archive: Uint8Array | AsyncIterable<Uint8Array>;
92
+ maxCompressedBytes: number;
93
+ maxExtractedBytes: number;
94
+ maxEntries: number;
95
+ /**
96
+ * Ceiling for the UTF-8 bytes of every effective entry path and symlink
97
+ * target, 1 to 134217728 (the fixed ci-spec ceiling, and the default).
98
+ */
99
+ maxPathMetadataBytes?: number;
100
+ }
101
+ export interface IGitManagerArchiveInspectionOptions {
102
+ signal?: AbortSignal;
103
+ }
104
+ /** Metadata recomputed from an accepted archive; nothing is taken on trust. */
105
+ export interface IGitManagerArchiveInspection {
106
+ sizeBytes: number;
107
+ sha256: string;
108
+ extractedSizeBytes: number;
109
+ entryCount: number;
110
+ pathMetadataBytes: number;
111
+ }
112
+ /**
113
+ * Stable codes of `GitManagerError`.
114
+ *
115
+ * - `invalidObjectId`: an object ID is not a full lowercase SHA-1 or SHA-256.
116
+ * - `objectNotFound`: the object does not exist in the repository.
117
+ * - `objectNotCommit`: the object exists but is not a commit.
118
+ * - `ancestryBudgetExceeded`: the ancestry check exceeded `timeoutMs`; the
119
+ * answer is unknown, never `false`.
120
+ * - `archiveLimitExceeded`: the archive exceeds a requested limit.
121
+ * - `archiveRejected`: the archive breaks a format, path, or link rule.
122
+ */
123
+ export type TGitManagerErrorCode = 'invalidObjectId' | 'objectNotFound' | 'objectNotCommit' | 'ancestryBudgetExceeded' | 'archiveLimitExceeded' | 'archiveRejected';
70
124
  export interface IGitManagerRepositoryLfsPointer {
71
125
  oid: string;
72
126
  sizeBytes: number;
@@ -1,9 +1,12 @@
1
1
  import * as plugins from './plugins.js';
2
- import type { IGitManagerCommitDetail, IGitManagerCommitRequest, IGitManagerCommitSummary, IGitManagerDiff, IGitManagerDiffRequest, IGitManagerFileContent, IGitManagerImportMirrorRequest, IGitManagerInitBareRepositoryRequest, IGitManagerListCommitsRequest, IGitManagerListTagsRequest, IGitManagerMergeRequest, IGitManagerMergeResult, IGitManagerReadFileRequest, IGitManagerRefRequest, IGitManagerProtectedRefsApplyResult, IGitManagerSmartHttpRepositoryProtectedRefs, IGitManagerRepositoryBundle, IGitManagerRepositoryArchive, IGitManagerRepositoryArchiveOptions, IGitManagerRepositoryArchiveRequest, IGitManagerRepositoryLfsPointer, IGitManagerRepositoryServiceRequest, IGitManagerRepositoryInfo, IGitManagerRepositorySelector, IGitManagerRestoreBundleRequest, IGitManagerSmartHttpRepositoryAccess, IGitManagerSmartHttpRepositoryVisibility, IGitManagerStatus, IGitManagerTag, IGitManagerTagRequest, IGitManagerTreeEntry, IGitManagerTreeRequest, IRustGitManagerConfig, TGitManagerRefInventoryResult, IRustGitManagerRepositoryServiceDescriptor } from './models.js';
2
+ import type { IGitManagerAncestryRequest, IGitManagerCommitDetail, IGitManagerCommitRequest, IGitManagerCommitSummary, IGitManagerDiff, IGitManagerDiffRequest, IGitManagerFileContent, IGitManagerImportMirrorRequest, IGitManagerInitBareRepositoryRequest, IGitManagerListCommitsRequest, IGitManagerListTagsRequest, IGitManagerMergeRequest, IGitManagerMergeResult, IGitManagerReadFileRequest, IGitManagerRefRequest, IGitManagerProtectedRefsApplyResult, IGitManagerSmartHttpRepositoryProtectedRefs, IGitManagerRepositoryBundle, IGitManagerRepositoryArchive, IGitManagerRepositoryArchiveOptions, IGitManagerRepositoryArchiveRequest, IGitManagerRepositoryLfsPointer, IGitManagerRepositoryServiceRequest, IGitManagerRepositoryInfo, IGitManagerRepositorySelector, IGitManagerRestoreBundleRequest, IGitManagerSmartHttpRepositoryAccess, IGitManagerSmartHttpRepositoryVisibility, IGitManagerStatus, IGitManagerTag, IGitManagerTagRequest, IGitManagerTreeEntry, IGitManagerTreeRequest, IRustGitManagerConfig, TGitManagerRefInventoryResult, IRustGitManagerRepositoryServiceDescriptor } from './models.js';
3
3
  export declare class RustGitManagerBridge extends plugins.EventEmitter {
4
4
  private bridge;
5
+ private binaryLocator;
5
6
  constructor();
6
7
  spawn(): Promise<boolean>;
8
+ /** Resolves the engine binary the bridge spawns, for its one-shot modes. */
9
+ findBinary(): Promise<string>;
7
10
  kill(): void;
8
11
  terminate(): Promise<void>;
9
12
  get running(): boolean;
@@ -27,6 +30,7 @@ export declare class RustGitManagerBridge extends plugins.EventEmitter {
27
30
  mergeRepository(requestArg: IGitManagerMergeRequest): Promise<IGitManagerMergeResult>;
28
31
  listTree(requestArg: IGitManagerTreeRequest): Promise<IGitManagerTreeEntry[]>;
29
32
  readFile(requestArg: IGitManagerReadFileRequest): Promise<IGitManagerFileContent>;
33
+ isAncestor(requestArg: IGitManagerAncestryRequest): Promise<boolean>;
30
34
  listCommits(requestArg: IGitManagerListCommitsRequest): Promise<IGitManagerCommitSummary[]>;
31
35
  getCommit(requestArg: IGitManagerCommitRequest): Promise<IGitManagerCommitDetail>;
32
36
  getDiff(requestArg: IGitManagerDiffRequest): Promise<IGitManagerDiff>;
@@ -1,4 +1,5 @@
1
1
  import * as plugins from './plugins.js';
2
+ import { GitManagerError, isGitManagerErrorCode } from './classes.gitmanagererror.js';
2
3
  function getPackageRoot() {
3
4
  const thisDir = plugins.path.dirname(plugins.url.fileURLToPath(import.meta.url));
4
5
  return plugins.path.resolve(thisDir, '..');
@@ -22,14 +23,31 @@ function buildLocalPaths() {
22
23
  paths.push(plugins.path.resolve(process.cwd(), 'rust', 'target', 'debug', 'rustgitmanager'));
23
24
  return paths;
24
25
  }
26
+ /**
27
+ * Rethrows a Rust response that carries a stable GitManager error code as a
28
+ * `GitManagerError`; every other failure is rethrown unchanged.
29
+ */
30
+ const rethrowCodedError = (errorArg) => {
31
+ if (errorArg instanceof plugins.smartrust.RustBridgeRequestError
32
+ && isGitManagerErrorCode(errorArg.responseErrorCode)) {
33
+ throw new GitManagerError(errorArg.responseErrorCode, errorArg.message, {
34
+ cause: errorArg,
35
+ });
36
+ }
37
+ throw errorArg;
38
+ };
25
39
  export class RustGitManagerBridge extends plugins.EventEmitter {
26
40
  constructor() {
27
41
  super();
28
- this.bridge = new plugins.smartrust.RustBridge({
42
+ const binaryLocatorOptions = {
29
43
  binaryName: 'rustgitmanager',
30
44
  envVarName: 'GITMANAGER_RUST_BINARY',
31
45
  platformPackagePrefix: '@foss.global/gitmanager',
32
46
  localPaths: buildLocalPaths(),
47
+ };
48
+ this.binaryLocator = new plugins.smartrust.RustBinaryLocator(binaryLocatorOptions);
49
+ this.bridge = new plugins.smartrust.RustBridge({
50
+ ...binaryLocatorOptions,
33
51
  requestTimeoutMs: 130000,
34
52
  readyTimeoutMs: 10000,
35
53
  });
@@ -43,6 +61,14 @@ export class RustGitManagerBridge extends plugins.EventEmitter {
43
61
  async spawn() {
44
62
  return this.bridge.spawn();
45
63
  }
64
+ /** Resolves the engine binary the bridge spawns, for its one-shot modes. */
65
+ async findBinary() {
66
+ const binaryPath = await this.binaryLocator.findBinary();
67
+ if (!binaryPath) {
68
+ throw new Error('Unable to locate the rustgitmanager binary');
69
+ }
70
+ return binaryPath;
71
+ }
46
72
  kill() {
47
73
  this.bridge.kill();
48
74
  }
@@ -164,6 +190,19 @@ export class RustGitManagerBridge extends plugins.EventEmitter {
164
190
  async readFile(requestArg) {
165
191
  return this.bridge.sendCommand('readFile', requestArg);
166
192
  }
193
+ async isAncestor(requestArg) {
194
+ const result = await this.bridge.sendCommand('isAncestor', {
195
+ org: requestArg.org,
196
+ repo: requestArg.repo,
197
+ ancestorObjectId: requestArg.ancestorObjectId,
198
+ descendantObjectId: requestArg.descendantObjectId,
199
+ ...(requestArg.timeoutMs === undefined ? {} : { timeoutMs: requestArg.timeoutMs }),
200
+ }).catch(rethrowCodedError);
201
+ if (typeof result !== 'boolean') {
202
+ throw new Error('Rust GitManager returned an invalid ancestry result');
203
+ }
204
+ return result;
205
+ }
167
206
  async listCommits(requestArg) {
168
207
  return this.bridge.sendCommand('listCommits', requestArg);
169
208
  }
@@ -180,4 +219,4 @@ export class RustGitManagerBridge extends plugins.EventEmitter {
180
219
  return this.bridge.sendCommand('getTag', requestArg);
181
220
  }
182
221
  }
183
- //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicnVzdC1naXRtYW5hZ2VyLWJyaWRnZS5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uL3RzL3J1c3QtZ2l0bWFuYWdlci1icmlkZ2UudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsT0FBTyxLQUFLLE9BQU8sTUFBTSxjQUFjLENBQUM7QUFtRnhDLFNBQVMsY0FBYztJQUNyQixNQUFNLE9BQU8sR0FBRyxPQUFPLENBQUMsSUFBSSxDQUFDLE9BQU8sQ0FBQyxPQUFPLENBQUMsR0FBRyxDQUFDLGFBQWEsQ0FBQyxNQUFNLENBQUMsSUFBSSxDQUFDLEdBQUcsQ0FBQyxDQUFDLENBQUM7SUFDakYsT0FBTyxPQUFPLENBQUMsSUFBSSxDQUFDLE9BQU8sQ0FBQyxPQUFPLEVBQUUsSUFBSSxDQUFDLENBQUM7QUFDN0MsQ0FBQztBQUVELFNBQVMsdUJBQXVCO0lBQzlCLE1BQU0sT0FBTyxHQUEyQixFQUFFLEdBQUcsRUFBRSxPQUFPLEVBQUUsS0FBSyxFQUFFLE9BQU8sRUFBRSxDQUFDO0lBQ3pFLE1BQU0sS0FBSyxHQUEyQixFQUFFLEtBQUssRUFBRSxPQUFPLEVBQUUsTUFBTSxFQUFFLE9BQU8sRUFBRSxDQUFDO0lBQzFFLE1BQU0sRUFBRSxHQUFHLEtBQUssQ0FBQyxPQUFPLENBQUMsUUFBUSxDQUFDLENBQUM7SUFDbkMsTUFBTSxJQUFJLEdBQUcsT0FBTyxDQUFDLE9BQU8sQ0FBQyxJQUFJLENBQUMsQ0FBQztJQUNuQyxPQUFPLEVBQUUsSUFBSSxJQUFJLENBQUMsQ0FBQyxDQUFDLEdBQUcsRUFBRSxJQUFJLElBQUksRUFBRSxDQUFDLENBQUMsQ0FBQyxJQUFJLENBQUM7QUFDN0MsQ0FBQztBQUVELFNBQVMsZUFBZTtJQUN0QixNQUFNLFdBQVcsR0FBRyxjQUFjLEVBQUUsQ0FBQztJQUNyQyxNQUFNLE1BQU0sR0FBRyx1QkFBdUIsRUFBRSxDQUFDO0lBQ3pDLE1BQU0sS0FBSyxHQUFhLEVBQUUsQ0FBQztJQUUzQixJQUFJLE1BQU0sRUFBRSxD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
222
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicnVzdC1naXRtYW5hZ2VyLWJyaWRnZS5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uL3RzL3J1c3QtZ2l0bWFuYWdlci1icmlkZ2UudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsT0FBTyxLQUFLLE9BQU8sTUFBTSxjQUFjLENBQUM7QUFDeEMsT0FBTyxFQUFFLGVBQWUsRUFBRSxxQkFBcUIsRUFBRSxNQUFNLDhCQUE4QixDQUFDO0FBcUZ0RixTQUFTLGNBQWM7SUFDckIsTUFBTSxPQUFPLEdBQUcsT0FBTyxDQUFDLElBQUksQ0FBQyxPQUFPLENBQUMsT0FBTyxDQUFDLEdBQUcsQ0FBQyxhQUFhLENBQUMsTUFBTSxDQUFDLElBQUksQ0FBQyxHQUFHLENBQUMsQ0FBQyxDQUFDO0lBQ2pGLE9BQU8sT0FBTyxDQUFDLElBQUksQ0FBQyxPQUFPLENBQUMsT0FBTyxFQUFFLElBQUksQ0FBQyxDQUFDO0FBQzdDLENBQUM7QUFFRCxTQUFTLHVCQUF1QjtJQUM5QixNQUFNLE9BQU8sR0FBMkIsRUFBRSxHQUFHLEVBQUUsT0FBTyxFQUFFLEtBQUssRUFBRSxPQUFPLEVBQUUsQ0FBQztJQUN6RSxNQUFNLEtBQUssR0FBMkIsRUFBRSxLQUFLLEVBQUUsT0FBTyxFQUFFLE1BQU0sRUFBRSxPQUFPLEVBQUUsQ0FBQztJQUMxRSxNQUFNLEVBQUUsR0FBRyxLQUFLLENBQUMsT0FBTyxDQUFDLFFBQVEsQ0FBQyxDQUFDO0lBQ25DLE1BQU0sSUFBSSxHQUFHLE9BQU8sQ0FBQyxPQUFPLENBQUMsSUFBSSxDQUFDLENBQUM7SUFDbkMsT0FBTyxFQUFFLElBQUksSUFBSSxDQUFDLENBQUMsQ0FBQyxHQUFHLEVBQUUsSUFBSSxJQUFJLEVBQUUsQ0FBQyxDQUFDLENBQUMsSUFBSSxDQUFDO0FBQzdDLENBQUM7QUFFRCxTQUFTLGVBQWU7SUFDdEIsTUFBTSxXQUFXLEdBQUcsY0FBYyxFQUFFLENBQUM7SUFDckMsTUFBTSxNQUFNLEdBQUcsdUJBQXVCLEVBQUUsQ0FB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TSxLQUFLLFNBQVMsRUFBRSxDQUFDO1lBQ2hDLE1BQU0sSUFBSSxLQUFLLENBQUMscURBQXFELENBQUMsQ0FBQztRQUN6RSxDQUFDO1FBQ0QsT0FBTyxNQUFNLENBQUM7SUFDaEIsQ0FBQztJQUVNLEtBQUssQ0FBQyxXQUFXLENBQUMsVUFBeUM7UUFDaEUsT0FBTyxJQUFJLENBQUMsTUFBTSxDQUFDLFdBQVcsQ0FBQyxhQUFhLEVBQUUsVUFBVSxDQUFDLENBQUM7SUFDNUQsQ0FBQztJQUVNLEtBQUssQ0FBQyxTQUFTLENBQUMsVUFBb0M7UUFDekQsT0FBTyxJQUFJLENBQUMsTUFBTSxDQUFDLFdBQVcsQ0FBQyxXQUFXLEVBQUUsVUFBVSxDQUFDLENBQUM7SUFDMUQsQ0FBQztJQUVNLEtBQUssQ0FBQyxPQUFPLENBQUMsVUFBa0M7UUFDckQsT0FBTyxJQUFJLENBQUMsTUFBTSxDQUFDLFdBQVcsQ0FBQyxTQUFTLEVBQUUsVUFBVSxDQUFDLENBQUM7SUFDeEQsQ0FBQztJQUVNLEtBQUssQ0FBQyxRQUFRLENBQUMsVUFBc0M7UUFDMUQsT0FBTyxJQUFJLENBQUMsTUFBTSxDQUFDLFdBQVcsQ0FBQyxVQUFVLEVBQUUsVUFBVSxDQUFDLENBQUM7SUFDekQsQ0FBQztJQUVNLEtBQUssQ0FBQyxNQUFNLENBQUMsVUFBaUM7UUFDbkQsT0FBTyxJQUFJLENBQUMsTUFBTSxDQUFDLFdBQVcsQ0FBQyxRQUFRLEVBQUUsVUFBVSxDQUFDLENBQUM7SUFDdkQsQ0FBQztDQUNGIn0=
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@foss.global/gitmanager",
3
- "version": "2.0.0",
3
+ "version": "2.1.0",
4
4
  "private": false,
5
5
  "description": "Headless Git manager for the foss.global Git platform, with a TypeScript API and Rust engine.",
6
6
  "exports": {
@@ -14,13 +14,13 @@
14
14
  "@push.rocks/smartrust": "^1.7.0"
15
15
  },
16
16
  "devDependencies": {
17
- "@foss.global/ci-spec": "0.2.0",
17
+ "@foss.global/ci-spec": "1.1.1",
18
18
  "@git.zone/tsbuild": "^5.0.0",
19
19
  "@git.zone/tsdoc": "^2.2.2",
20
- "@git.zone/tsrun": "^3.0.0",
21
- "@git.zone/tsrust": "^1.15.0",
22
- "@git.zone/tstest": "^6.2.0",
23
- "@types/node": "^26.6.1"
20
+ "@git.zone/tsrun": "^3.0.1",
21
+ "@git.zone/tsrust": "^1.15.1",
22
+ "@git.zone/tstest": "^6.3.2",
23
+ "@types/node": "^26.6.2"
24
24
  },
25
25
  "files": [
26
26
  "ts/**/*",
package/readme.md CHANGED
@@ -283,6 +283,39 @@ if (!readiness.ready) {
283
283
 
284
284
  When Git Smart HTTP is running on a loopback bind, the probe targets the engine's own `GET /healthz` route out-of-band, so readiness stays available while the serial management channel is busy with a long-running command such as a mirror import. Repository archive creation also keeps management status and cancellation responsive while preserving serial admission for every other stateful command. Without a loopback Smart HTTP route, including a service bound to a non-loopback address with `allowUnsafeNonLoopback`, one shared bounded `getStatus` round trip per instance is used; that fallback queues behind other in-flight management commands and a Smart HTTP probe failure never falls through to it. Failure reasons are `not_running`, `starting`, `stopping`, `aborted`, `timeout`, and `unavailable`; the result matches the readiness contracts of `@push.rocks/smartdata` and `@push.rocks/smartbucket`.
285
285
 
286
+ ## Commit ancestry
287
+
288
+ `isAncestor()` answers `git merge-base --is-ancestor` exactly: whether the ancestor commit is reachable from the descendant commit, where a commit is its own ancestor. Both object IDs must be full lowercase SHA-1 or SHA-256 IDs of commits in the repository; revision expressions, abbreviations, and tag objects are refused, so peel a tag before asking.
289
+
290
+ ```ts
291
+ // Throws GitManagerError (error.code) unless the answer is exact.
292
+ const reachableFromMain = await gitManager.isAncestor({
293
+ org: 'foss.global',
294
+ repo: 'gitmanager',
295
+ ancestorObjectId: taggedCommitId,
296
+ descendantObjectId: mainTipCommitId,
297
+ timeoutMs: 10_000,
298
+ });
299
+ ```
300
+
301
+ `timeoutMs` is the work budget for the object lookup and the history walk together: 1 to 60000 ms, 10000 by default. When it runs out, the Git process is killed and the call throws. It never returns `false` for anything but an exact answer; the typed codes are `invalidObjectId`, `objectNotFound`, `objectNotCommit`, and `ancestryBudgetExceeded`. The check runs on the serial management channel like the other read APIs.
302
+
303
+ ## Archive inspection
304
+
305
+ `inspectArchive()` validates a `tar.gz` on the receiving side of a transfer, for example a CI artifact or cache upload. It streams the archive, given as a `Uint8Array` or any async iterable of `Uint8Array` chunks such as a Node stream or a fetch body, into a dedicated engine process. That process checks every entry against the rules GitManager applies to its own source archives and the `@foss.global/ci-spec` archive conformance vectors: safe relative paths, no duplicates or late ancestors, only regular files, directories and symlinks whose targets stay inside the root (including through chains), USTAR and supported PAX metadata only, zeroed padding, no trailing data or concatenated gzip members, and the fixed 134,217,728-byte path-metadata ceiling.
306
+
307
+ ```ts
308
+ const inspection = await gitManager.inspectArchive({
309
+ archive: uploadStream,
310
+ maxCompressedBytes: 16 * 1024 ** 3,
311
+ maxExtractedBytes: 128 * 1024 ** 3,
312
+ maxEntries: 1_000_000,
313
+ }, { signal });
314
+ // { sizeBytes, sha256, extractedSizeBytes, entryCount, pathMetadataBytes }
315
+ ```
316
+
317
+ The metadata is recomputed from the bytes, so compare it with what the sender declared rather than trusting the declaration. Every limit is inclusive and must be a non-negative safe integer; `maxPathMetadataBytes` is 1 to 134217728 and defaults to the ceiling. A refused archive throws a `GitManagerError` with code `archiveLimitExceeded` (a requested limit) or `archiveRejected` (a format, path, or link rule), and the inspector stops reading at the first violation and closes the source. Aborting the signal or calling `stop()` kills the inspector; the call rejects only after the process exited. The inspection does not use the management channel, so several can run at once.
318
+
286
319
  ## Public API
287
320
 
288
321
  - `GitManager`: TypeScript facade for the Rust engine lifecycle.
@@ -310,9 +343,12 @@ When Git Smart HTTP is running on a loopback bind, the probe targets the engine'
310
343
  - `GitManager.getDiff()`: returns a structured unified diff between a head ref and its first parent or an explicit base ref.
311
344
  - `GitManager.listTags()`: lists lightweight and annotated tags with target object metadata.
312
345
  - `GitManager.getTag()`: reads one tag by exact name.
346
+ - `GitManager.isAncestor()`: answers exact commit ancestry within a work budget, with typed errors for anything else.
347
+ - `GitManager.inspectArchive(request, { signal? })`: streams a receiver-side `tar.gz` through the engine inspector and returns its recomputed metadata or a typed refusal.
313
348
  - `GitManager.running`: local facade running state.
314
349
  - `RustGitManagerBridge`: lower-level typed bridge around `@push.rocks/smartrust`.
315
- - `IGitManagerOptions`, `IGitManagerStatus`, `IGitManagerReadinessOptions`, `TGitManagerReadinessResult`, `TGitManagerReadinessFailureReason`, `IRustGitManagerConfig`, `IGitManagerProtectedRefRule`, `IGitManagerSmartHttpRepositoryProtectedRefs`, `TGitManagerProtectedRefsApplyStatus`, and `IGitManagerProtectedRefsApplyResult`: exported option, status, readiness, and protected-ref contracts.
350
+ - `GitManagerError` and `TGitManagerErrorCode`: typed failures with a stable `code`; `gitManagerArchivePathMetadataCeilingBytes` is the fixed archive path-metadata ceiling.
351
+ - `IGitManagerAncestryRequest`, `IGitManagerArchiveInspectionRequest`, `IGitManagerArchiveInspectionOptions`, `IGitManagerArchiveInspection`, `IGitManagerOptions`, `IGitManagerStatus`, `IGitManagerReadinessOptions`, `TGitManagerReadinessResult`, `TGitManagerReadinessFailureReason`, `IRustGitManagerConfig`, `IGitManagerProtectedRefRule`, `IGitManagerSmartHttpRepositoryProtectedRefs`, `TGitManagerProtectedRefsApplyStatus`, and `IGitManagerProtectedRefsApplyResult`: exported option, status, readiness, and protected-ref contracts.
316
352
 
317
353
  Repository/read DTO contracts are re-exported from the published `@foss.global/interfaces` package.
318
354
 
@@ -339,7 +375,7 @@ pnpm run test:rust
339
375
  pnpm run build
340
376
  ```
341
377
 
342
- `pnpm test` runs the `pretest` hook first, which builds the Rust binaries with `tsrust`, and then runs lifecycle, bounded readiness, local mirror/read API, raw upload-pack/receive-pack preparation, clone/fetch denial, trusted receive-pack denial, explicit policy initialization, protected create/delete/non-fast-forward enforcement, inherited self-test hardening, restart replay, and real push tests through `smartrust`. `test/test.refinventory.node.ts` covers every ref namespace, peeled and nested tags, symbolic and dangling symbolic refs, unborn HEAD, prefix selection and exact lookup, ref names that are not valid UTF-8, pagination of 1,001 refs, stale and foreign cursors, 50,000 loose and packed refs, and a bundle round trip across namespaces. `test/test.pushlimits.node.ts` covers fixed-length and chunked Smart HTTP pushes over the limit, a complete pack followed by late excess bytes, an over-limit SSH-style receive-pack, an SSH-style receive-pack cut after Git already moved a ref (the update stays), and a bounded `stop()` while a service's output is never read, the rejected cases proving the refs unchanged, no Git or hook process left, no uncaught stream error, the engine serving, and a later push succeeding.
378
+ `pnpm test` runs the `pretest` hook first, which builds the Rust binaries with `tsrust`, and then runs lifecycle, bounded readiness, local mirror/read API, raw upload-pack/receive-pack preparation, clone/fetch denial, trusted receive-pack denial, explicit policy initialization, protected create/delete/non-fast-forward enforcement, inherited self-test hardening, restart replay, and real push tests through `smartrust`. `test/test.refinventory.node.ts` covers every ref namespace, peeled and nested tags, symbolic and dangling symbolic refs, unborn HEAD, prefix selection and exact lookup, ref names that are not valid UTF-8, pagination of 1,001 refs, stale and foreign cursors, 50,000 loose and packed refs, and a bundle round trip across namespaces. `test/test.pushlimits.node.ts` covers fixed-length and chunked Smart HTTP pushes over the limit, a complete pack followed by late excess bytes, an over-limit SSH-style receive-pack, an SSH-style receive-pack cut after Git already moved a ref (the update stays), and a bounded `stop()` while a service's output is never read, the rejected cases proving the refs unchanged, no Git or hook process left, no uncaught stream error, the engine serving, and a later push succeeding. `test/test.ancestry.node.ts` covers ancestors, the reverse direction, siblings, merges, unrelated histories, the same commit, invalid, missing and non-commit objects, a budget exceeded on a 20,000-commit history next to the exact answer, and a stalled walk whose Git process is killed. `test/test.archiveinspection.node.ts` runs every ci-spec archive conformance vector through `inspectArchive()` as bytes and as a chunked stream, the compressed limit at its boundary, an 8 MiB streamed archive, an early refusal that closes the source, and abort, source failure and `stop()` during an inspection.
343
379
 
344
380
  ## License and Legal Information
345
381
 
@@ -3,6 +3,6 @@
3
3
  */
4
4
  export const commitinfo = {
5
5
  name: '@foss.global/gitmanager',
6
- version: '2.0.0',
6
+ version: '2.1.0',
7
7
  description: 'Headless Git manager for the foss.global Git platform, with a TypeScript API and Rust engine.'
8
8
  }
@@ -0,0 +1,245 @@
1
+ import * as plugins from './plugins.js';
2
+ import { GitManagerError } from './classes.gitmanagererror.js';
3
+ import type {
4
+ IGitManagerArchiveInspection,
5
+ IGitManagerArchiveInspectionRequest,
6
+ } from './models.js';
7
+
8
+ /** The fixed ci-spec path-metadata ceiling of every archive. */
9
+ export const gitManagerArchivePathMetadataCeilingBytes = 134_217_728;
10
+
11
+ const maxInspectorOutputBytes = 64 * 1024;
12
+
13
+ type TInspectorSettlement =
14
+ | { kind: 'closed'; code: number | null; signal: NodeJS.Signals | null }
15
+ | { kind: 'spawnFailed'; error: Error };
16
+
17
+ const assertLimit = (
18
+ nameArg: string,
19
+ valueArg: number,
20
+ minimumArg: number,
21
+ maximumArg: number,
22
+ ): void => {
23
+ if (!Number.isSafeInteger(valueArg) || valueArg < minimumArg || valueArg > maximumArg) {
24
+ throw new TypeError(
25
+ `${nameArg} must be a safe integer between ${minimumArg} and ${maximumArg}.`,
26
+ );
27
+ }
28
+ };
29
+
30
+ const isRecord = (valueArg: unknown): valueArg is Record<string, unknown> =>
31
+ typeof valueArg === 'object' && valueArg !== null && !Array.isArray(valueArg);
32
+
33
+ const isCount = (valueArg: unknown): valueArg is number =>
34
+ typeof valueArg === 'number' && Number.isSafeInteger(valueArg) && valueArg >= 0;
35
+
36
+ /**
37
+ * Parses the inspector's single JSON result: the recomputed metadata of an
38
+ * accepted archive, or a thrown `GitManagerError` for a refused one.
39
+ */
40
+ const parseInspectorResult = (stdoutArg: string): IGitManagerArchiveInspection => {
41
+ let parsed: unknown;
42
+ try {
43
+ parsed = JSON.parse(stdoutArg);
44
+ } catch (error) {
45
+ throw new Error('Archive inspector returned invalid JSON', { cause: error });
46
+ }
47
+ if (isRecord(parsed) && parsed.status === 'rejected' && typeof parsed.message === 'string') {
48
+ if (parsed.reason === 'limitExceeded') {
49
+ throw new GitManagerError('archiveLimitExceeded', parsed.message);
50
+ }
51
+ if (parsed.reason === 'invalid') {
52
+ throw new GitManagerError('archiveRejected', parsed.message);
53
+ }
54
+ }
55
+ if (
56
+ isRecord(parsed)
57
+ && parsed.status === 'accepted'
58
+ && isCount(parsed.sizeBytes)
59
+ && typeof parsed.sha256 === 'string'
60
+ && /^[0-9a-f]{64}$/.test(parsed.sha256)
61
+ && isCount(parsed.extractedSizeBytes)
62
+ && isCount(parsed.entryCount)
63
+ && isCount(parsed.pathMetadataBytes)
64
+ ) {
65
+ return {
66
+ sizeBytes: parsed.sizeBytes,
67
+ sha256: parsed.sha256,
68
+ extractedSizeBytes: parsed.extractedSizeBytes,
69
+ entryCount: parsed.entryCount,
70
+ pathMetadataBytes: parsed.pathMetadataBytes,
71
+ };
72
+ }
73
+ throw new Error('Archive inspector returned an invalid result');
74
+ };
75
+
76
+ /**
77
+ * Streams one archive into a dedicated engine inspector process and returns
78
+ * its recomputed metadata. The inspector reads until the archive is accepted
79
+ * or the first violation; on a refusal the remaining input is not read and
80
+ * the source is closed. Aborting `signalArg` kills the inspector and rejects
81
+ * with the abort reason once the process has exited.
82
+ */
83
+ export const runArchiveInspection = async (
84
+ binaryPathArg: string,
85
+ requestArg: IGitManagerArchiveInspectionRequest,
86
+ signalArg: AbortSignal,
87
+ ): Promise<IGitManagerArchiveInspection> => {
88
+ const maxPathMetadataBytes =
89
+ requestArg.maxPathMetadataBytes ?? gitManagerArchivePathMetadataCeilingBytes;
90
+ assertLimit('maxCompressedBytes', requestArg.maxCompressedBytes, 0, Number.MAX_SAFE_INTEGER);
91
+ assertLimit('maxExtractedBytes', requestArg.maxExtractedBytes, 0, Number.MAX_SAFE_INTEGER);
92
+ assertLimit('maxEntries', requestArg.maxEntries, 0, Number.MAX_SAFE_INTEGER);
93
+ assertLimit(
94
+ 'maxPathMetadataBytes',
95
+ maxPathMetadataBytes,
96
+ 1,
97
+ gitManagerArchivePathMetadataCeilingBytes,
98
+ );
99
+ if (signalArg.aborted) {
100
+ throw signalArg.reason;
101
+ }
102
+
103
+ const child = plugins.childProcess.spawn(binaryPathArg, [
104
+ '--inspect-archive-stream',
105
+ '--max-compressed-bytes',
106
+ String(requestArg.maxCompressedBytes),
107
+ '--max-extracted-bytes',
108
+ String(requestArg.maxExtractedBytes),
109
+ '--max-entries',
110
+ String(requestArg.maxEntries),
111
+ '--max-path-metadata-bytes',
112
+ String(maxPathMetadataBytes),
113
+ ], { stdio: ['pipe', 'pipe', 'pipe'] });
114
+ const killInspector = (): void => {
115
+ if (child.exitCode === null && child.signalCode === null) {
116
+ child.kill('SIGKILL');
117
+ }
118
+ };
119
+
120
+ const settled = new Promise<TInspectorSettlement>((resolve) => {
121
+ child.once('error', (error) => {
122
+ killInspector();
123
+ resolve({ kind: 'spawnFailed', error });
124
+ });
125
+ child.once('close', (code, signal) => resolve({ kind: 'closed', code, signal }));
126
+ });
127
+ let inspectorSettled = false;
128
+ void settled.then(() => {
129
+ inspectorSettled = true;
130
+ });
131
+
132
+ const stdoutChunks: Buffer[] = [];
133
+ const stderrChunks: Buffer[] = [];
134
+ let outputBytes = 0;
135
+ let outputOverflow = false;
136
+ const collect = (chunksArg: Buffer[]) => (chunkArg: Buffer): void => {
137
+ outputBytes += chunkArg.byteLength;
138
+ if (outputBytes > maxInspectorOutputBytes) {
139
+ outputOverflow = true;
140
+ killInspector();
141
+ return;
142
+ }
143
+ chunksArg.push(chunkArg);
144
+ };
145
+ child.stdout.on('data', collect(stdoutChunks));
146
+ child.stderr.on('data', collect(stderrChunks));
147
+
148
+ // A write error is the inspector closing its input after a refusal (or
149
+ // dying); its exit status and result decide the outcome.
150
+ child.stdin.on('error', () => undefined);
151
+
152
+ let aborted = false;
153
+ const abortInspection = (): void => {
154
+ aborted = true;
155
+ killInspector();
156
+ };
157
+ signalArg.addEventListener('abort', abortInspection, { once: true });
158
+
159
+ let delivered = false;
160
+ let sourceFailure: { error: unknown } | undefined;
161
+ const waitForDrain = (): Promise<unknown> => Promise.race([
162
+ new Promise<void>((resolve) => {
163
+ const done = (): void => {
164
+ child.stdin.off('drain', done);
165
+ child.stdin.off('close', done);
166
+ resolve();
167
+ };
168
+ child.stdin.once('drain', done);
169
+ child.stdin.once('close', done);
170
+ }),
171
+ settled,
172
+ ]);
173
+ const feed = async (): Promise<void> => {
174
+ const archive = requestArg.archive;
175
+ if (archive instanceof Uint8Array) {
176
+ child.stdin.end(archive);
177
+ delivered = true;
178
+ return;
179
+ }
180
+ const iterator = archive[Symbol.asyncIterator]();
181
+ let exhausted = false;
182
+ try {
183
+ while (!inspectorSettled) {
184
+ const step = await Promise.race([
185
+ iterator.next().then((result) => ({ kind: 'chunk' as const, result })),
186
+ settled.then(() => ({ kind: 'settled' as const })),
187
+ ]);
188
+ if (step.kind === 'settled' || inspectorSettled) {
189
+ return;
190
+ }
191
+ if (step.result.done) {
192
+ exhausted = true;
193
+ child.stdin.end();
194
+ delivered = true;
195
+ return;
196
+ }
197
+ if (!(step.result.value instanceof Uint8Array)) {
198
+ throw new TypeError('The archive stream must yield Uint8Array chunks.');
199
+ }
200
+ if (!child.stdin.write(step.result.value)) {
201
+ await waitForDrain();
202
+ }
203
+ }
204
+ } catch (error) {
205
+ sourceFailure = { error };
206
+ killInspector();
207
+ } finally {
208
+ if (!exhausted && !sourceFailure) {
209
+ // Close the source like a `for await` loop that stops early; a
210
+ // stalled source must not hold the inspection open.
211
+ void Promise.resolve(iterator.return?.()).catch(() => undefined);
212
+ }
213
+ }
214
+ };
215
+
216
+ try {
217
+ const [settlement] = await Promise.all([settled, feed()]);
218
+ if (aborted) {
219
+ throw signalArg.reason;
220
+ }
221
+ if (sourceFailure) {
222
+ throw sourceFailure.error;
223
+ }
224
+ if (settlement.kind === 'spawnFailed') {
225
+ throw new Error('Failed to spawn the archive inspector', { cause: settlement.error });
226
+ }
227
+ if (outputOverflow) {
228
+ throw new Error('Archive inspector output exceeded its limit');
229
+ }
230
+ if (settlement.code !== 0) {
231
+ const stderr = Buffer.concat(stderrChunks).toString('utf8').trim();
232
+ throw new Error(
233
+ `Archive inspector failed with ${settlement.signal ?? `status ${settlement.code}`}`
234
+ + (stderr ? `: ${stderr}` : ''),
235
+ );
236
+ }
237
+ const inspection = parseInspectorResult(Buffer.concat(stdoutChunks).toString('utf8'));
238
+ if (!delivered) {
239
+ throw new Error('Archive inspector accepted an archive that was not completely delivered');
240
+ }
241
+ return inspection;
242
+ } finally {
243
+ signalArg.removeEventListener('abort', abortInspection);
244
+ }
245
+ };
@@ -1,5 +1,9 @@
1
1
  import * as plugins from './plugins.js';
2
2
  import type {
3
+ IGitManagerAncestryRequest,
4
+ IGitManagerArchiveInspection,
5
+ IGitManagerArchiveInspectionOptions,
6
+ IGitManagerArchiveInspectionRequest,
3
7
  IGitManagerCommitDetail,
4
8
  IGitManagerCommitRequest,
5
9
  IGitManagerCommitSummary,
@@ -41,6 +45,7 @@ import type {
41
45
  } from './models.js';
42
46
  import { RustGitManagerBridge } from './rust-gitmanager-bridge.js';
43
47
  import { GitManagerRepositoryService } from './classes.repositoryservice.js';
48
+ import { runArchiveInspection } from './classes.archiveinspection.js';
44
49
 
45
50
  const readinessDefaultTimeoutMs = 2_000;
46
51
  const readinessMaximumTimeoutMs = 30_000;
@@ -56,6 +61,7 @@ export class GitManager extends plugins.EventEmitter {
56
61
  private stopPromise: Promise<void> | null = null;
57
62
  private config: IRustGitManagerConfig;
58
63
  private readonly repositoryServices = new Set<GitManagerRepositoryService>();
64
+ private readonly archiveInspections = new Map<AbortController, Promise<unknown>>();
59
65
  private lifecycleEpoch = 0;
60
66
  private smartHttpProbeUrl: string | null = null;
61
67
  private readonly activeReadinessControllers = new Set<AbortController>();
@@ -168,12 +174,14 @@ export class GitManager extends plugins.EventEmitter {
168
174
  }
169
175
  if (!this.bridge.running) {
170
176
  await this.closeRepositoryServices();
177
+ await this.abortArchiveInspections();
171
178
  await this.bridge.terminate();
172
179
  this.started = false;
173
180
  return;
174
181
  }
175
182
  try {
176
183
  await this.closeRepositoryServices();
184
+ await this.abortArchiveInspections();
177
185
  await this.bridge.stopGitManager();
178
186
  } finally {
179
187
  await this.bridge.terminate();
@@ -440,6 +448,51 @@ export class GitManager extends plugins.EventEmitter {
440
448
  return this.bridge.readFile(requestArg);
441
449
  }
442
450
 
451
+ /**
452
+ * Answers `git merge-base --is-ancestor` exactly within a work budget.
453
+ * Anything short of an exact answer throws a `GitManagerError`
454
+ * (`invalidObjectId`, `objectNotFound`, `objectNotCommit`,
455
+ * `ancestryBudgetExceeded`); it never degrades to `false`.
456
+ */
457
+ public async isAncestor(requestArg: IGitManagerAncestryRequest): Promise<boolean> {
458
+ this.assertRunning();
459
+ return this.bridge.isAncestor(requestArg);
460
+ }
461
+
462
+ /**
463
+ * Inspects a receiver-side `tar.gz` archive in a dedicated engine process:
464
+ * recomputes its compressed size, SHA-256, extracted bytes, entry count,
465
+ * and path metadata bytes while enforcing the limits and the ci-spec path
466
+ * and link rules. A refused archive throws a `GitManagerError` with code
467
+ * `archiveLimitExceeded` or `archiveRejected`; an invalid limit throws a
468
+ * `TypeError`. `stop()` aborts inspections in flight.
469
+ */
470
+ public async inspectArchive(
471
+ requestArg: IGitManagerArchiveInspectionRequest,
472
+ optionsArg: IGitManagerArchiveInspectionOptions = {},
473
+ ): Promise<IGitManagerArchiveInspection> {
474
+ this.assertRunning();
475
+ const controller = new AbortController();
476
+ const callerSignal = optionsArg.signal;
477
+ const abortForCaller = (): void => {
478
+ controller.abort(callerSignal?.reason);
479
+ };
480
+ if (callerSignal?.aborted) {
481
+ throw callerSignal.reason;
482
+ }
483
+ callerSignal?.addEventListener('abort', abortForCaller, { once: true });
484
+ const inspection = this.bridge.findBinary().then((binaryPath) => {
485
+ return runArchiveInspection(binaryPath, requestArg, controller.signal);
486
+ });
487
+ this.archiveInspections.set(controller, inspection);
488
+ try {
489
+ return await inspection;
490
+ } finally {
491
+ callerSignal?.removeEventListener('abort', abortForCaller);
492
+ this.archiveInspections.delete(controller);
493
+ }
494
+ }
495
+
443
496
  public async listCommits(requestArg: IGitManagerListCommitsRequest): Promise<IGitManagerCommitSummary[]> {
444
497
  this.assertRunning();
445
498
  return this.bridge.listCommits(requestArg);
@@ -675,6 +728,15 @@ export class GitManager extends plugins.EventEmitter {
675
728
  }
676
729
  }
677
730
 
731
+ private async abortArchiveInspections(): Promise<void> {
732
+ const inspections = [...this.archiveInspections];
733
+ this.archiveInspections.clear();
734
+ for (const [controller] of inspections) {
735
+ controller.abort(new Error('GitManager stopped during archive inspection'));
736
+ }
737
+ await Promise.all(inspections.map(([, inspection]) => inspection.catch(() => undefined)));
738
+ }
739
+
678
740
  private async closeRepositoryServices(): Promise<void> {
679
741
  const services = [...this.repositoryServices];
680
742
  this.repositoryServices.clear();