@inerrata-corporation/errata 2.0.7-dev.2782 → 2.0.7-dev.2821

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.
Files changed (2) hide show
  1. package/errata.mjs +131 -23
  2. package/package.json +1 -1
package/errata.mjs CHANGED
@@ -25433,8 +25433,17 @@ var init_client = __esm({
25433
25433
  }
25434
25434
  /** Resolve-or-create the org's project for a repo locator (ambient link,
25435
25435
  * PJ-entity). Requires user identity on the JWT — login-gated like actAs. */
25436
- async resolveProject(locator, name2) {
25437
- return this.json("POST", "/api/gate/projects/resolve", name2 ? { locator, name: name2 } : { locator });
25436
+ async resolveProject(locator, name2, rootCommitSha) {
25437
+ return this.json("POST", "/api/gate/projects/resolve", {
25438
+ locator,
25439
+ ...name2 ? { name: name2 } : {},
25440
+ ...rootCommitSha ? { rootCommitSha } : {}
25441
+ });
25442
+ }
25443
+ /** PJ-claim-fingerprint: tell an already-linked project its repo's root commit.
25444
+ * Org-scoped and record-if-absent server-side; never creates a project. */
25445
+ async recordProjectRootCommit(projectId, rootCommitSha) {
25446
+ return this.json("POST", "/api/gate/projects/fingerprint", { projectId, rootCommitSha });
25438
25447
  }
25439
25448
  /** PJ-ingest-history: report the history stage so the console mirrors it;
25440
25449
  * the answer carries the console's standing Stop. Org-scoped; foreign ids 404. */
@@ -62967,7 +62976,7 @@ var require_mock_interceptor = __commonJS({
62967
62976
  var require_mock_client = __commonJS({
62968
62977
  "../../node_modules/.pnpm/undici@6.24.1/node_modules/undici/lib/mock/mock-client.js"(exports, module2) {
62969
62978
  "use strict";
62970
- var { promisify: promisify2 } = __require("node:util");
62979
+ var { promisify: promisify3 } = __require("node:util");
62971
62980
  var Client = require_client();
62972
62981
  var { buildMockDispatch } = require_mock_utils();
62973
62982
  var {
@@ -63007,7 +63016,7 @@ var require_mock_client = __commonJS({
63007
63016
  return new MockInterceptor(opts, this[kDispatches]);
63008
63017
  }
63009
63018
  async [kClose]() {
63010
- await promisify2(this[kOriginalClose])();
63019
+ await promisify3(this[kOriginalClose])();
63011
63020
  this[kConnected] = 0;
63012
63021
  this[kMockAgent][Symbols.kClients].delete(this[kOrigin]);
63013
63022
  }
@@ -63020,7 +63029,7 @@ var require_mock_client = __commonJS({
63020
63029
  var require_mock_pool = __commonJS({
63021
63030
  "../../node_modules/.pnpm/undici@6.24.1/node_modules/undici/lib/mock/mock-pool.js"(exports, module2) {
63022
63031
  "use strict";
63023
- var { promisify: promisify2 } = __require("node:util");
63032
+ var { promisify: promisify3 } = __require("node:util");
63024
63033
  var Pool = require_pool();
63025
63034
  var { buildMockDispatch } = require_mock_utils();
63026
63035
  var {
@@ -63060,7 +63069,7 @@ var require_mock_pool = __commonJS({
63060
63069
  return new MockInterceptor(opts, this[kDispatches]);
63061
63070
  }
63062
63071
  async [kClose]() {
63063
- await promisify2(this[kOriginalClose])();
63072
+ await promisify3(this[kOriginalClose])();
63064
63073
  this[kConnected] = 0;
63065
63074
  this[kMockAgent][Symbols.kClients].delete(this[kOrigin]);
63066
63075
  }
@@ -79922,7 +79931,7 @@ function createWatchBreaker(deps) {
79922
79931
  }
79923
79932
 
79924
79933
  // src/engine.ts
79925
- var DAEMON_VERSION = true ? "2.0.7-dev.2782" : "2.0.0-alpha.0";
79934
+ var DAEMON_VERSION = true ? "2.0.7-dev.2821" : "2.0.0-alpha.0";
79926
79935
  var IGNORED_PATH = /[\\/](?:\.git|node_modules|\.errata|\.claude|\.codex|\.cursor|\.turbo|\.next|dist|coverage|test-results|playwright-report|__pycache__)(?:[\\/]|$)/;
79927
79936
  var IGNORED_NOISE = /castalia\.db|eventlog\.sqlite|turn-cursors/;
79928
79937
  var GIT_OP_MUTE_MS = 4e3;
@@ -83703,17 +83712,47 @@ init_identity2();
83703
83712
 
83704
83713
  // src/project-link.ts
83705
83714
  init_profile2();
83706
- async function ensureProjectLink(root, profile, client) {
83715
+ import { execFile as execFile3 } from "node:child_process";
83716
+ import { promisify } from "node:util";
83717
+ var execFileAsync = promisify(execFile3);
83718
+ async function readRootCommit(root) {
83719
+ try {
83720
+ const { stdout } = await execFileAsync("git", ["rev-list", "--max-parents=0", "HEAD"], {
83721
+ cwd: root,
83722
+ encoding: "utf8",
83723
+ timeout: 15e3,
83724
+ windowsHide: true
83725
+ });
83726
+ const sha2 = stdout.split(/\s+/).filter(Boolean).pop();
83727
+ return sha2 && /^[0-9a-f]{40,64}$/i.test(sha2) ? sha2.toLowerCase() : null;
83728
+ } catch {
83729
+ return null;
83730
+ }
83731
+ }
83732
+ async function ensureProjectLink(root, profile, client, rootCommit = readRootCommit) {
83707
83733
  if (profile.projectLinkDisabled) return { linked: false, reason: "disabled" };
83708
83734
  const locator = profile.repoLocator;
83709
83735
  if (!locator) return { linked: false, reason: "no-locator" };
83710
83736
  if (profile.projectId && profile.projectLocator === locator) {
83737
+ if (profile.rootCommitReportedFor !== profile.projectId && client.recordProjectRootCommit) {
83738
+ const sha2 = await rootCommit(root);
83739
+ if (sha2) {
83740
+ try {
83741
+ await client.recordProjectRootCommit(profile.projectId, sha2);
83742
+ profile.rootCommitReportedFor = profile.projectId;
83743
+ saveProfile(root, profile);
83744
+ } catch {
83745
+ }
83746
+ }
83747
+ }
83711
83748
  return { linked: false, reason: "already-linked" };
83712
83749
  }
83713
83750
  try {
83714
- const res = await client.resolveProject(locator, profile.name);
83751
+ const sha2 = await rootCommit(root);
83752
+ const res = await client.resolveProject(locator, profile.name, sha2 ?? void 0);
83715
83753
  profile.projectId = res.project.id;
83716
83754
  profile.projectLocator = locator;
83755
+ if (sha2) profile.rootCommitReportedFor = res.project.id;
83717
83756
  saveProfile(root, profile);
83718
83757
  return { linked: true, projectId: res.project.id, created: res.created };
83719
83758
  } catch (err2) {
@@ -84065,10 +84104,10 @@ async function hydrateProject(opts) {
84065
84104
  // src/project-history.ts
84066
84105
  init_src7();
84067
84106
  init_src();
84068
- import { execFile as execFile3 } from "node:child_process";
84107
+ import { execFile as execFile4 } from "node:child_process";
84069
84108
  import { readFileSync as readFileSync32 } from "node:fs";
84070
84109
  import { join as join36 } from "node:path";
84071
- import { promisify } from "node:util";
84110
+ import { promisify as promisify2 } from "node:util";
84072
84111
 
84073
84112
  // src/history-walk.ts
84074
84113
  var LOCKFILE_NAMES = /* @__PURE__ */ new Set([
@@ -84442,14 +84481,14 @@ var GitBlobReader = class {
84442
84481
 
84443
84482
  // src/project-history.ts
84444
84483
  init_profile2();
84445
- var execFileAsync = promisify(execFile3);
84484
+ var execFileAsync2 = promisify2(execFile4);
84446
84485
  var HISTORY_EXTRACTOR_VERSION = "history-2";
84447
84486
  var LEASE_POLL_MS = 2e4;
84448
84487
  var RECOVERY_DEADLINE_MS = 15 * 6e4;
84449
84488
  var MAX_ANCHORS_PER_TOKEN = 20;
84450
84489
  var DEFAULT_LOOKAHEAD = 2;
84451
84490
  async function defaultGit(root, args2) {
84452
- const { stdout } = await execFileAsync("git", ["-C", root, ...args2], { maxBuffer: 64 * 1024 * 1024, windowsHide: true });
84491
+ const { stdout } = await execFileAsync2("git", ["-C", root, ...args2], { maxBuffer: 64 * 1024 * 1024, windowsHide: true });
84453
84492
  return stdout;
84454
84493
  }
84455
84494
  function readIgnoreLines(root, profile) {
@@ -84995,16 +85034,28 @@ async function loginOAuthDevice(opts) {
84995
85034
  if (approvedInstallation !== opts.installationId) {
84996
85035
  throw new Error("approved installation does not match the terminal request");
84997
85036
  }
84998
- const resourceResponse = await fetchFn(device.resource, {
85037
+ return exchangeDeviceSession({
85038
+ fetchFn,
85039
+ resourceUrl: device.resource,
85040
+ deviceSession,
85041
+ installationId: approvedInstallation,
85042
+ clientId,
85043
+ scope
85044
+ });
85045
+ }
85046
+ async function exchangeDeviceSession(opts) {
85047
+ const fetchFn = opts.fetchFn ?? fetch;
85048
+ const scope = opts.scope ?? DEFAULT_OAUTH_SCOPE;
85049
+ const resourceResponse = await fetchFn(opts.resourceUrl, {
84999
85050
  method: "POST",
85000
85051
  headers: {
85001
- authorization: `Bearer ${deviceSession}`,
85052
+ authorization: `Bearer ${opts.deviceSession}`,
85002
85053
  "content-type": "application/json",
85003
85054
  accept: "application/json"
85004
85055
  },
85005
85056
  body: JSON.stringify({
85006
- installationId: approvedInstallation,
85007
- clientId,
85057
+ installationId: opts.installationId,
85058
+ clientId: opts.clientId,
85008
85059
  scopes: scope.split(/\s+/).filter((value) => !["openid", "profile", "email", "offline_access"].includes(value))
85009
85060
  })
85010
85061
  });
@@ -85013,12 +85064,12 @@ async function loginOAuthDevice(opts) {
85013
85064
  throw new Error(`device resource exchange failed: HTTP ${resourceResponse.status}`);
85014
85065
  }
85015
85066
  return {
85016
- installationId: approvedInstallation,
85017
- clientId,
85018
- tokenEndpoint: device.resource,
85067
+ installationId: opts.installationId,
85068
+ clientId: opts.clientId,
85069
+ tokenEndpoint: opts.resourceUrl,
85019
85070
  tokens: {
85020
85071
  accessToken: resource["access_token"],
85021
- refreshToken: deviceSession,
85072
+ refreshToken: opts.deviceSession,
85022
85073
  ...typeof resource["expires_in"] === "number" ? { expiresInSec: resource["expires_in"] } : {}
85023
85074
  }
85024
85075
  };
@@ -85232,7 +85283,10 @@ async function linkThroughConsole(opts) {
85232
85283
  return {
85233
85284
  status: "approved",
85234
85285
  decision,
85235
- installationId: typeof payload?.["installation_id"] === "string" ? payload["installation_id"] : null
85286
+ installationId: typeof payload?.["installation_id"] === "string" ? payload["installation_id"] : null,
85287
+ deviceSession: typeof payload?.["access_token"] === "string" ? payload["access_token"] : null,
85288
+ resourceUrl: device.resource,
85289
+ clientId
85236
85290
  };
85237
85291
  }
85238
85292
  return { status: "expired" };
@@ -89222,6 +89276,50 @@ async function linkRegisteredWorkspaces(cfg) {
89222
89276
  }
89223
89277
  }
89224
89278
  }
89279
+ async function bindWorkspaceToLinkInstallation(cfg, out2, orgId) {
89280
+ const installationId = out2.installationId;
89281
+ if (!out2.deviceSession) {
89282
+ console.warn(
89283
+ ` note: the project is in another org; run \`errata login --device --installation ${installationId} --profile <name>\` then \`errata workspace bind . --profile <name>\``
89284
+ );
89285
+ return null;
89286
+ }
89287
+ try {
89288
+ const minted = await exchangeDeviceSession({
89289
+ resourceUrl: out2.resourceUrl,
89290
+ deviceSession: out2.deviceSession,
89291
+ installationId,
89292
+ clientId: out2.clientId
89293
+ });
89294
+ const existing = Object.entries(cfg.installationProfiles).find(([, p]) => p.installationId === installationId)?.[0];
89295
+ let profileName = existing ?? `link-${orgId.slice(0, 8)}`;
89296
+ for (let i2 = 2; !existing && cfg.installationProfiles[profileName]; i2++) profileName = `link-${orgId.slice(0, 8)}-${i2}`;
89297
+ const next = {
89298
+ ...cfg,
89299
+ installationProfiles: {
89300
+ ...cfg.installationProfiles,
89301
+ [profileName]: {
89302
+ installationId,
89303
+ cloudUrl: cfg.cloudUrl,
89304
+ apiKey: null,
89305
+ accessToken: minted.tokens.accessToken,
89306
+ refreshToken: minted.tokens.refreshToken ?? null,
89307
+ tokenEndpoint: minted.tokenEndpoint,
89308
+ oauthClientId: minted.clientId,
89309
+ activeAgent: null
89310
+ }
89311
+ }
89312
+ };
89313
+ saveConfig(next);
89314
+ setWorkspaceBinding(workspaceEntryForPath(ROOT)?.id ?? ROOT, profileName);
89315
+ return { config: next, profileName };
89316
+ } catch (err2) {
89317
+ console.warn(
89318
+ ` note: the project is linked, but this workspace could not take the other org's credential (${err2 instanceof Error ? err2.message : String(err2)}); run \`errata login --device --installation ${installationId} --profile <name>\` then \`errata workspace bind . --profile <name>\``
89319
+ );
89320
+ return null;
89321
+ }
89322
+ }
89225
89323
  async function cmdLink(args2) {
89226
89324
  const flag = (name2) => {
89227
89325
  const i2 = args2.indexOf(name2);
@@ -89301,12 +89399,22 @@ async function cmdLink(args2) {
89301
89399
  );
89302
89400
  await refreshLinkedCodexHooks(args2);
89303
89401
  if (applied.wroteIgnore) console.log("wrote .errataignore (starter) \u2014 edit it; it is your hard control");
89402
+ let reportClient = authedCloudClient(applied.config);
89403
+ if (out3.installationId && out3.installationId !== activeProfile?.installationId) {
89404
+ const bound = await bindWorkspaceToLinkInstallation(applied.config, out3, d.orgId);
89405
+ if (bound) {
89406
+ reportClient = authedCloudClientForProfile(bound.config, bound.profileName);
89407
+ console.log(
89408
+ `this workspace now uses installation ${out3.installationId.slice(0, 8)} (profile ${bound.profileName}) in org ${d.orgId.slice(0, 8)}; your machine default is unchanged`
89409
+ );
89410
+ }
89411
+ }
89304
89412
  if (!hasCloudCredential(cfg)) {
89305
89413
  console.log(" note: run `errata login` so this daemon can sync under the link and report the history stage.");
89306
89414
  return;
89307
89415
  }
89308
89416
  const consents = historyConsents(applied.profile);
89309
- const reported = await reportHistoryStage(authedCloudClient(applied.config), applied.profile, (line) => console.warn(line));
89417
+ const reported = await reportHistoryStage(reportClient, applied.profile, (line) => console.warn(line));
89310
89418
  if (reported?.consents) {
89311
89419
  const synced = applyConsents(applied.profile, applied.config, reported.consents, (/* @__PURE__ */ new Date()).toISOString());
89312
89420
  if (synced.changed) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@inerrata-corporation/errata",
3
- "version": "2.0.7-dev.2782",
3
+ "version": "2.0.7-dev.2821",
4
4
  "description": "errata - local-first observation engine for AI coding agents",
5
5
  "type": "module",
6
6
  "bin": {