@inerrata-corporation/errata 2.0.6-dev.1989 → 2.0.6-dev.2028

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.
@@ -15267,7 +15267,7 @@ var init_symbol_intent = __esm({
15267
15267
  });
15268
15268
 
15269
15269
  // ../../packages/shared/src/wire.ts
15270
- var CLOUD_NODE_LABELS, CLOUD_WRITABLE_NODE_LABELS, PROJECT_ONLY_NODE_LABELS, PROJECT_ONLY_NODE_LABEL_SET, PROJECT_ONLY_EDGE_TYPES, PROJECT_ONLY_EDGE_TYPE_SET, CLOUD_WRITABLE_NODE_LABEL_SET, CLOUD_EDGE_TYPES, INGEST_SOURCES, INGEST_EXTRACTION_SOURCES, CastaliaIngestNodeSchema, RouteContextCountWireSchema, CastaliaIngestEdgeSchema, SymbolSummarySidecarSchema, CastaliaIngestPayloadSchema;
15270
+ var CLOUD_NODE_LABELS, CLOUD_WRITABLE_NODE_LABELS, PROJECT_ONLY_NODE_LABELS, PROJECT_ONLY_NODE_LABEL_SET, PROJECT_ONLY_EDGE_TYPES, PROJECT_ONLY_EDGE_TYPE_SET, CLOUD_WRITABLE_NODE_LABEL_SET, CLOUD_EDGE_TYPES, INGEST_SOURCES, INGEST_EXTRACTION_SOURCES, CastaliaIngestNodeSchema, RouteContextCountWireSchema, ANCHOR_COMMIT_SHA_RE, ANCHOR_BODY_HASH_RE, CastaliaIngestEdgeSchema, SymbolSummarySidecarSchema, CastaliaIngestPayloadSchema;
15271
15271
  var init_wire = __esm({
15272
15272
  "../../packages/shared/src/wire.ts"() {
15273
15273
  "use strict";
@@ -15368,6 +15368,8 @@ var init_wire = __esm({
15368
15368
  independent: external_exports.number().int().min(0).optional(),
15369
15369
  inferredIndependent: external_exports.number().int().min(0).optional()
15370
15370
  });
15371
+ ANCHOR_COMMIT_SHA_RE = /^[0-9a-f]{7,40}$/;
15372
+ ANCHOR_BODY_HASH_RE = /^[0-9a-f]{8,64}$/;
15371
15373
  CastaliaIngestEdgeSchema = external_exports.object({
15372
15374
  fromCanonicalId: external_exports.string().min(6).max(160),
15373
15375
  toCanonicalId: external_exports.string().min(6).max(160),
@@ -15386,8 +15388,8 @@ var init_wire = __esm({
15386
15388
  * (reanchor) compares the hash with its local symbol's `bodyHash` to
15387
15389
  * classify the anchor current / pre-change; absent ⇒ the pre-versioning
15388
15390
  * contract, unverified. Only meaningful on `ANCHORED_AT`. */
15389
- observedAtCommit: external_exports.string().regex(/^[0-9a-f]{7,40}$/).optional(),
15390
- symbolContentHash: external_exports.string().regex(/^[0-9a-f]{8,64}$/).optional()
15391
+ observedAtCommit: external_exports.string().regex(ANCHOR_COMMIT_SHA_RE).optional(),
15392
+ symbolContentHash: external_exports.string().regex(ANCHOR_BODY_HASH_RE).optional()
15391
15393
  }).optional()
15392
15394
  });
15393
15395
  SymbolSummarySidecarSchema = external_exports.record(
package/errata.mjs CHANGED
@@ -15711,6 +15711,15 @@ function provenanceBand(source) {
15711
15711
  function isSeedProvenance(source) {
15712
15712
  return provenanceBand(source) === "seed";
15713
15713
  }
15714
+ function anchorVersionAttrs(attrs) {
15715
+ if (!attrs) return {};
15716
+ const commit = attrs["observedAtCommit"];
15717
+ const hash2 = attrs["symbolContentHash"];
15718
+ return {
15719
+ ...typeof commit === "string" && ANCHOR_COMMIT_SHA_RE.test(commit) ? { observedAtCommit: commit } : {},
15720
+ ...typeof hash2 === "string" && ANCHOR_BODY_HASH_RE.test(hash2) ? { symbolContentHash: hash2 } : {}
15721
+ };
15722
+ }
15714
15723
  function validateCastaliaPayload(p) {
15715
15724
  const errors = [];
15716
15725
  const labelByCid = /* @__PURE__ */ new Map();
@@ -15740,7 +15749,7 @@ function summarizeIngestResult(r) {
15740
15749
  ...r.patternReconciliation ? { patternReconciliation: r.patternReconciliation } : {}
15741
15750
  };
15742
15751
  }
15743
- var CLOUD_NODE_LABELS, CLOUD_WRITABLE_NODE_LABELS, PROJECT_ONLY_NODE_LABELS, PROJECT_ONLY_NODE_LABEL_SET, PROJECT_ONLY_EDGE_TYPES, PROJECT_ONLY_EDGE_TYPE_SET, CLOUD_WRITABLE_NODE_LABEL_SET, CLOUD_EDGE_TYPES, INGEST_SOURCES, INGEST_EXTRACTION_SOURCES, QUARANTINE_EXTRACTION_SOURCE_PREFIX, MAX_NODES_PER_PAYLOAD, MAX_EDGES_PER_PAYLOAD, CastaliaIngestNodeSchema, RouteContextCountWireSchema, CastaliaIngestEdgeSchema, SymbolSummarySidecarSchema, CastaliaIngestPayloadSchema;
15752
+ var CLOUD_NODE_LABELS, CLOUD_WRITABLE_NODE_LABELS, PROJECT_ONLY_NODE_LABELS, PROJECT_ONLY_NODE_LABEL_SET, PROJECT_ONLY_EDGE_TYPES, PROJECT_ONLY_EDGE_TYPE_SET, CLOUD_WRITABLE_NODE_LABEL_SET, CLOUD_EDGE_TYPES, INGEST_SOURCES, INGEST_EXTRACTION_SOURCES, QUARANTINE_EXTRACTION_SOURCE_PREFIX, MAX_NODES_PER_PAYLOAD, MAX_EDGES_PER_PAYLOAD, CastaliaIngestNodeSchema, RouteContextCountWireSchema, ANCHOR_COMMIT_SHA_RE, ANCHOR_BODY_HASH_RE, CastaliaIngestEdgeSchema, SymbolSummarySidecarSchema, CastaliaIngestPayloadSchema;
15744
15753
  var init_wire = __esm({
15745
15754
  "../../packages/shared/src/wire.ts"() {
15746
15755
  "use strict";
@@ -15844,6 +15853,8 @@ var init_wire = __esm({
15844
15853
  independent: external_exports.number().int().min(0).optional(),
15845
15854
  inferredIndependent: external_exports.number().int().min(0).optional()
15846
15855
  });
15856
+ ANCHOR_COMMIT_SHA_RE = /^[0-9a-f]{7,40}$/;
15857
+ ANCHOR_BODY_HASH_RE = /^[0-9a-f]{8,64}$/;
15847
15858
  CastaliaIngestEdgeSchema = external_exports.object({
15848
15859
  fromCanonicalId: external_exports.string().min(6).max(160),
15849
15860
  toCanonicalId: external_exports.string().min(6).max(160),
@@ -15862,8 +15873,8 @@ var init_wire = __esm({
15862
15873
  * (reanchor) compares the hash with its local symbol's `bodyHash` to
15863
15874
  * classify the anchor current / pre-change; absent ⇒ the pre-versioning
15864
15875
  * contract, unverified. Only meaningful on `ANCHORED_AT`. */
15865
- observedAtCommit: external_exports.string().regex(/^[0-9a-f]{7,40}$/).optional(),
15866
- symbolContentHash: external_exports.string().regex(/^[0-9a-f]{8,64}$/).optional()
15876
+ observedAtCommit: external_exports.string().regex(ANCHOR_COMMIT_SHA_RE).optional(),
15877
+ symbolContentHash: external_exports.string().regex(ANCHOR_BODY_HASH_RE).optional()
15867
15878
  }).optional()
15868
15879
  });
15869
15880
  SymbolSummarySidecarSchema = external_exports.record(
@@ -16334,6 +16345,8 @@ __export(src_exports, {
16334
16345
  ALL_EDGE_TYPES: () => ALL_EDGE_TYPES,
16335
16346
  ALL_NODE_LABELS: () => ALL_NODE_LABELS,
16336
16347
  AMBIGUOUS_ALIASES: () => AMBIGUOUS_ALIASES,
16348
+ ANCHOR_BODY_HASH_RE: () => ANCHOR_BODY_HASH_RE,
16349
+ ANCHOR_COMMIT_SHA_RE: () => ANCHOR_COMMIT_SHA_RE,
16337
16350
  ANCHOR_REFUSAL_REASONS: () => ANCHOR_REFUSAL_REASONS,
16338
16351
  BELIEF_EDGES: () => BELIEF_EDGES,
16339
16352
  BRIDGE_EDGES: () => BRIDGE_EDGES,
@@ -16392,6 +16405,7 @@ __export(src_exports, {
16392
16405
  VALIDATION_SOURCES: () => VALIDATION_SOURCES,
16393
16406
  aliasesLongestFirst: () => aliasesLongestFirst,
16394
16407
  allEntities: () => allEntities,
16408
+ anchorVersionAttrs: () => anchorVersionAttrs,
16395
16409
  assertGraphEventVocabulary: () => assertGraphEventVocabulary,
16396
16410
  buildSpine: () => buildSpine,
16397
16411
  canonicalClaimId: () => canonicalClaimId,
@@ -24536,7 +24550,7 @@ function toWirePayload(batch, runId) {
24536
24550
  ...typeof e.confidence === "number" ? { confidence: clamp012(e.confidence) } : {},
24537
24551
  ...perContext ? { perContext } : {},
24538
24552
  // PJ-anchor-version: the two opaque version stamps ride on anchors only.
24539
- ...e.type === "ANCHORED_AT" ? wireAnchorVersion(e.attrs) : {}
24553
+ ...e.type === "ANCHORED_AT" ? anchorVersionAttrs(e.attrs) : {}
24540
24554
  }
24541
24555
  };
24542
24556
  });
@@ -24572,15 +24586,6 @@ function textOrThrow(value, field) {
24572
24586
  if (!text) throw new TypeError(`${field} must be a non-empty string`);
24573
24587
  return text;
24574
24588
  }
24575
- function wireAnchorVersion(attrs) {
24576
- if (!attrs) return {};
24577
- const commit = attrs["observedAtCommit"];
24578
- const hash2 = attrs["symbolContentHash"];
24579
- return {
24580
- ...typeof commit === "string" && HEX_SHA.test(commit) ? { observedAtCommit: commit } : {},
24581
- ...typeof hash2 === "string" && HEX_BODY.test(hash2) ? { symbolContentHash: hash2 } : {}
24582
- };
24583
- }
24584
24589
  function optionalEdgeAttrs(confidence) {
24585
24590
  return typeof confidence === "number" ? { confidence: clamp012(confidence) } : void 0;
24586
24591
  }
@@ -24706,15 +24711,13 @@ function provenanceHeaders(provenance) {
24706
24711
  ...provenance.agentModel ? { "x-inerrata-agent-model": provenance.agentModel } : {}
24707
24712
  };
24708
24713
  }
24709
- var asWireCount, HEX_SHA, HEX_BODY, INGEST_NODE_CHUNK, E6B_PUBLIC_CONTEXT_SENTINEL, CloudClient, CloudError;
24714
+ var asWireCount, INGEST_NODE_CHUNK, E6B_PUBLIC_CONTEXT_SENTINEL, CloudClient, CloudError;
24710
24715
  var init_client = __esm({
24711
24716
  "../../packages/cloud-client/src/client.ts"() {
24712
24717
  "use strict";
24713
24718
  init_oauth();
24714
24719
  init_src();
24715
24720
  asWireCount = (n) => typeof n === "number" && Number.isFinite(n) && n >= 0 ? Math.floor(n) : null;
24716
- HEX_SHA = /^[0-9a-f]{7,40}$/;
24717
- HEX_BODY = /^[0-9a-f]{8,64}$/;
24718
24721
  INGEST_NODE_CHUNK = 8;
24719
24722
  E6B_PUBLIC_CONTEXT_SENTINEL = "00000000-0000-4e6b-8000-000000000e6b";
24720
24723
  CloudClient = class {
@@ -77005,7 +77008,7 @@ function createWatchBreaker(deps) {
77005
77008
  }
77006
77009
 
77007
77010
  // src/engine.ts
77008
- var DAEMON_VERSION = true ? "2.0.6-dev.1989" : "2.0.0-alpha.0";
77011
+ var DAEMON_VERSION = true ? "2.0.6-dev.2028" : "2.0.0-alpha.0";
77009
77012
  var IGNORED_PATH = /[\\/](?:\.git|node_modules|\.errata|\.claude|\.codex|\.cursor|\.turbo|\.next|dist|coverage|test-results|playwright-report|__pycache__)(?:[\\/]|$)/;
77010
77013
  var IGNORED_NOISE = /castalia\.db|eventlog\.sqlite|turn-cursors/;
77011
77014
  var GIT_OP_MUTE_MS = 4e3;
@@ -79334,7 +79337,7 @@ init_identity2();
79334
79337
  // src/multi.ts
79335
79338
  init_dist();
79336
79339
  init_src5();
79337
- import { existsSync as existsSync30, readFileSync as readFileSync30, unlinkSync as unlinkSync3, writeFileSync as writeFileSync22 } from "node:fs";
79340
+ import { existsSync as existsSync31, readFileSync as readFileSync30, unlinkSync as unlinkSync3, writeFileSync as writeFileSync23 } from "node:fs";
79338
79341
 
79339
79342
  // src/principle-sync.ts
79340
79343
  init_src5();
@@ -79729,15 +79732,6 @@ function contributedWireId(n) {
79729
79732
  }
79730
79733
  var WIRE_CANONICAL_ID_MIN = 6;
79731
79734
  var SHIP_TERMINAL_REFUSALS = 3;
79732
- function anchorVersionAttrs(attrs) {
79733
- if (!attrs) return {};
79734
- const commit = attrs["observedAtCommit"];
79735
- const hash2 = attrs["symbolContentHash"];
79736
- return {
79737
- ...typeof commit === "string" && /^[0-9a-f]{7,40}$/.test(commit) ? { observedAtCommit: commit } : {},
79738
- ...typeof hash2 === "string" && /^[0-9a-f]{8,64}$/.test(hash2) ? { symbolContentHash: hash2 } : {}
79739
- };
79740
- }
79741
79735
  function refusalFingerprint(reason, description) {
79742
79736
  return digest(`${reason}\0${description}`);
79743
79737
  }
@@ -82299,10 +82293,14 @@ function applyStopRequest(profile, now = () => /* @__PURE__ */ new Date()) {
82299
82293
  }
82300
82294
  };
82301
82295
  }
82302
- async function startHistoryStage(root, profile, client, walk10 = {}) {
82296
+ async function startHistoryStage(root, input, client, walk10 = {}) {
82297
+ let profile = input;
82303
82298
  if (!profile.projectId) return { started: false, reason: "no-project", profile };
82304
- const consents = historyConsents(profile);
82305
82299
  const response = await reportHistoryStage(client, profile);
82300
+ if (response?.consents && walk10.applyConsents) {
82301
+ profile = walk10.applyConsents(profile, response.consents);
82302
+ }
82303
+ const consents = historyConsents(profile);
82306
82304
  if (response?.stopRequested && profile.historyStage && profile.historyStage.state !== "refused") {
82307
82305
  const stopped = applyStopRequest(profile);
82308
82306
  await reportHistoryStage(client, stopped);
@@ -82313,17 +82311,414 @@ async function startHistoryStage(root, profile, client, walk10 = {}) {
82313
82311
  return { started: true, profile: outcome.profile };
82314
82312
  }
82315
82313
 
82314
+ // src/link-flow.ts
82315
+ init_cloud_auth();
82316
+ import { createHash as createHash15 } from "node:crypto";
82317
+ import { execFileSync as execFileSync4 } from "node:child_process";
82318
+ import { existsSync as existsSync29, writeFileSync as writeFileSync22 } from "node:fs";
82319
+ import { join as join34 } from "node:path";
82320
+
82321
+ // src/oauth-login.ts
82322
+ init_src7();
82323
+ init_cloud_auth();
82324
+ import { createServer } from "node:http";
82325
+ var DEFAULT_OAUTH_SCOPE = "openid profile graph:read graph:write mcp:tools";
82326
+ var strip = (u) => u.replace(/\/+$/, "");
82327
+ function deviceEndpoints(tokenEndpoint) {
82328
+ const token = new URL(tokenEndpoint);
82329
+ const authRoot = token.pathname.replace(/\/(?:oauth2|mcp)\/token\/?$/, "");
82330
+ if (authRoot === token.pathname) throw new Error("authorization server exposes no device endpoint");
82331
+ const prefix = authRoot.replace(/\/api\/auth\/?$/, "");
82332
+ const at = (path2) => new URL(`${path2}`, `${token.origin}${prefix || "/"}`).toString();
82333
+ return {
82334
+ code: at(`${prefix}/api/auth/device/code`),
82335
+ token: at(`${prefix}/api/auth/device/token`),
82336
+ resource: at(`${prefix}/api/gate/session-token`)
82337
+ };
82338
+ }
82339
+ async function loginOAuthDevice(opts) {
82340
+ const fetchFn = opts.fetchFn ?? fetch;
82341
+ const endpoints = await discoverEndpoints(opts.cloudUrl, fetchFn);
82342
+ const device = deviceEndpoints(endpoints.tokenEndpoint);
82343
+ const clientId = oauthClientId();
82344
+ const scope = opts.scope ?? DEFAULT_OAUTH_SCOPE;
82345
+ const codeResponse = await fetchFn(device.code, {
82346
+ method: "POST",
82347
+ headers: { "content-type": "application/json", accept: "application/json" },
82348
+ body: JSON.stringify({ client_id: clientId, scope })
82349
+ });
82350
+ if (!codeResponse.ok) {
82351
+ throw new Error(`device authorization failed: HTTP ${codeResponse.status} ${(await codeResponse.text()).slice(0, 200)}`);
82352
+ }
82353
+ const code = await codeResponse.json();
82354
+ if (!code.device_code || !code.user_code || !code.verification_uri || !code.expires_in) {
82355
+ throw new Error("device authorization returned an incomplete response");
82356
+ }
82357
+ opts.onVerification?.({
82358
+ url: code.verification_uri_complete ?? code.verification_uri,
82359
+ userCode: code.user_code,
82360
+ expiresInSec: code.expires_in
82361
+ });
82362
+ const deadline = Date.now() + Math.min(code.expires_in * 1e3, opts.timeoutMs ?? Number.POSITIVE_INFINITY);
82363
+ let intervalMs = Math.max(2, code.interval ?? 5) * 1e3;
82364
+ let deviceSession = null;
82365
+ let approvedInstallation = null;
82366
+ while (Date.now() < deadline) {
82367
+ await (opts.sleep ?? ((ms) => new Promise((resolve7) => setTimeout(resolve7, ms))))(intervalMs);
82368
+ const poll = await fetchFn(device.token, {
82369
+ method: "POST",
82370
+ headers: { "content-type": "application/json", accept: "application/json" },
82371
+ body: JSON.stringify({
82372
+ grant_type: "urn:ietf:params:oauth:grant-type:device_code",
82373
+ device_code: code.device_code,
82374
+ client_id: clientId
82375
+ })
82376
+ });
82377
+ const payload = await poll.json().catch(() => null);
82378
+ if (!poll.ok) {
82379
+ const error48 = typeof payload?.["error"] === "string" ? payload["error"] : "device_poll_failed";
82380
+ if (error48 === "authorization_pending") continue;
82381
+ if (error48 === "slow_down") {
82382
+ intervalMs += 5e3;
82383
+ continue;
82384
+ }
82385
+ throw new Error(`device authorization failed: ${error48}`);
82386
+ }
82387
+ deviceSession = typeof payload?.["access_token"] === "string" ? payload["access_token"] : null;
82388
+ approvedInstallation = typeof payload?.["installation_id"] === "string" ? payload["installation_id"] : null;
82389
+ break;
82390
+ }
82391
+ if (!deviceSession || !approvedInstallation) throw new Error("device authorization expired");
82392
+ if (approvedInstallation !== opts.installationId) {
82393
+ throw new Error("approved installation does not match the terminal request");
82394
+ }
82395
+ const resourceResponse = await fetchFn(device.resource, {
82396
+ method: "POST",
82397
+ headers: {
82398
+ authorization: `Bearer ${deviceSession}`,
82399
+ "content-type": "application/json",
82400
+ accept: "application/json"
82401
+ },
82402
+ body: JSON.stringify({
82403
+ installationId: approvedInstallation,
82404
+ clientId,
82405
+ scopes: scope.split(/\s+/).filter((value) => !["openid", "profile", "email", "offline_access"].includes(value))
82406
+ })
82407
+ });
82408
+ const resource = await resourceResponse.json().catch(() => null);
82409
+ if (!resourceResponse.ok || typeof resource?.["access_token"] !== "string") {
82410
+ throw new Error(`device resource exchange failed: HTTP ${resourceResponse.status}`);
82411
+ }
82412
+ return {
82413
+ installationId: approvedInstallation,
82414
+ clientId,
82415
+ tokenEndpoint: device.resource,
82416
+ tokens: {
82417
+ accessToken: resource["access_token"],
82418
+ refreshToken: deviceSession,
82419
+ ...typeof resource["expires_in"] === "number" ? { expiresInSec: resource["expires_in"] } : {}
82420
+ }
82421
+ };
82422
+ }
82423
+ async function discoverEndpoints(cloudUrl, fetchFn = fetch) {
82424
+ let consoleUrl = cloudUrl;
82425
+ try {
82426
+ const prm = await fetchFn(`${strip(cloudUrl)}/.well-known/oauth-protected-resource`);
82427
+ if (prm.ok) {
82428
+ const j = await prm.json();
82429
+ if (j.authorization_servers?.[0]) consoleUrl = j.authorization_servers[0];
82430
+ }
82431
+ } catch {
82432
+ }
82433
+ try {
82434
+ const asm = await fetchFn(`${strip(consoleUrl)}/.well-known/oauth-authorization-server`);
82435
+ if (asm.ok) {
82436
+ const j = await asm.json();
82437
+ if (j.authorization_endpoint && j.token_endpoint) {
82438
+ return {
82439
+ authorizationEndpoint: j.authorization_endpoint,
82440
+ tokenEndpoint: j.token_endpoint,
82441
+ ...j.registration_endpoint ? { registrationEndpoint: j.registration_endpoint } : {}
82442
+ };
82443
+ }
82444
+ }
82445
+ } catch {
82446
+ }
82447
+ const base = strip(consoleUrl);
82448
+ return {
82449
+ authorizationEndpoint: `${base}/api/auth/oauth2/authorize`,
82450
+ tokenEndpoint: `${base}/api/auth/oauth2/token`
82451
+ };
82452
+ }
82453
+ function buildAuthorizeUrl(endpoint, p) {
82454
+ const u = new URL(endpoint);
82455
+ u.searchParams.set("response_type", "code");
82456
+ u.searchParams.set("client_id", p.clientId);
82457
+ u.searchParams.set("redirect_uri", p.redirectUri);
82458
+ u.searchParams.set("scope", p.scope);
82459
+ u.searchParams.set("state", p.state);
82460
+ u.searchParams.set("code_challenge", p.challenge);
82461
+ u.searchParams.set("code_challenge_method", p.method);
82462
+ return u.toString();
82463
+ }
82464
+ async function loginOAuthLoopback(opts) {
82465
+ const fetchFn = opts.fetchFn ?? fetch;
82466
+ const endpoints = await discoverEndpoints(opts.cloudUrl, fetchFn);
82467
+ const pkce = generatePkce();
82468
+ const state = randomState();
82469
+ const scope = opts.scope ?? DEFAULT_OAUTH_SCOPE;
82470
+ const { code, redirectUri, clientId } = await new Promise(
82471
+ (resolve7, reject) => {
82472
+ const server = createServer((req, res) => {
82473
+ const u = new URL(req.url ?? "/", "http://127.0.0.1");
82474
+ if (u.pathname !== "/callback") {
82475
+ res.writeHead(404);
82476
+ res.end();
82477
+ return;
82478
+ }
82479
+ const addr2 = server.address();
82480
+ const port = typeof addr2 === "object" && addr2 ? addr2.port : 0;
82481
+ const err2 = u.searchParams.get("error");
82482
+ const code2 = u.searchParams.get("code");
82483
+ const returnedState = u.searchParams.get("state");
82484
+ if (err2 || !code2 || returnedState !== state) {
82485
+ res.writeHead(400, { "content-type": "text/plain" });
82486
+ res.end(`login failed: ${err2 ?? (returnedState !== state ? "state_mismatch" : "no_code")}`);
82487
+ server.close();
82488
+ reject(new Error(err2 ?? "invalid_callback"));
82489
+ return;
82490
+ }
82491
+ res.writeHead(200, { "content-type": "text/html" });
82492
+ res.end("<html><body>inErrata: you're logged in. You can close this tab.</body></html>");
82493
+ server.close();
82494
+ resolve7({ code: code2, redirectUri: `http://127.0.0.1:${port}/callback`, clientId: activeClientId });
82495
+ });
82496
+ let activeClientId = oauthClientId();
82497
+ server.on("error", reject);
82498
+ server.listen(0, "127.0.0.1", () => {
82499
+ void (async () => {
82500
+ try {
82501
+ const addr2 = server.address();
82502
+ const port = typeof addr2 === "object" && addr2 ? addr2.port : 0;
82503
+ const redirectUri2 = `http://127.0.0.1:${port}/callback`;
82504
+ if (endpoints.registrationEndpoint) {
82505
+ const registered = await registerOAuthClient(fetchFn, endpoints.registrationEndpoint, {
82506
+ redirectUri: redirectUri2,
82507
+ scope,
82508
+ clientName: "errata daemon"
82509
+ });
82510
+ activeClientId = registered.clientId;
82511
+ }
82512
+ const url2 = buildAuthorizeUrl(endpoints.authorizationEndpoint, {
82513
+ clientId: activeClientId,
82514
+ redirectUri: redirectUri2,
82515
+ scope,
82516
+ state,
82517
+ challenge: pkce.challenge,
82518
+ method: pkce.method
82519
+ });
82520
+ if (opts.open) opts.open(url2);
82521
+ else console.log(`approve this login in your browser:
82522
+ ${url2}`);
82523
+ } catch (err2) {
82524
+ server.close();
82525
+ reject(err2);
82526
+ }
82527
+ })();
82528
+ });
82529
+ if (opts.timeoutMs) {
82530
+ setTimeout(() => {
82531
+ server.close();
82532
+ reject(new Error("login timed out \u2014 run `errata login` again"));
82533
+ }, opts.timeoutMs).unref?.();
82534
+ }
82535
+ }
82536
+ );
82537
+ const tokens = await exchangeAuthorizationCode(fetchFn, endpoints.tokenEndpoint, {
82538
+ code,
82539
+ codeVerifier: pkce.verifier,
82540
+ redirectUri,
82541
+ clientId
82542
+ });
82543
+ return { tokens, tokenEndpoint: endpoints.tokenEndpoint, clientId };
82544
+ }
82545
+
82546
+ // src/link-flow.ts
82547
+ function remoteFingerprint(locator) {
82548
+ return createHash15("sha256").update(locator).digest("hex");
82549
+ }
82550
+ function isDecision(value) {
82551
+ if (!value || typeof value !== "object") return false;
82552
+ const v = value;
82553
+ return typeof v["projectId"] === "string" && typeof v["locator"] === "string" && (v["visibility"] === "public" || v["visibility"] === "private") && typeof v["consents"] === "object" && v["consents"] !== null;
82554
+ }
82555
+ async function linkThroughConsole(opts) {
82556
+ const fetchFn = opts.fetchFn ?? fetch;
82557
+ const endpoints = await discoverEndpoints(opts.cloudUrl, fetchFn);
82558
+ const device = deviceEndpoints(endpoints.tokenEndpoint);
82559
+ const clientId = oauthClientId();
82560
+ const link = {
82561
+ locator: opts.facts.locator,
82562
+ remoteFingerprint: remoteFingerprint(opts.facts.locator),
82563
+ ...opts.facts.name ? { name: opts.facts.name } : {},
82564
+ ...opts.facts.rootCommitSha ? { rootCommitSha: opts.facts.rootCommitSha } : {},
82565
+ ...opts.facts.installationId ? { installationId: opts.facts.installationId } : {},
82566
+ ...opts.facts.agents && opts.facts.agents.length > 0 ? { agents: opts.facts.agents.slice(0, 12) } : {},
82567
+ ...typeof opts.facts.commitCount === "number" ? { commitCount: opts.facts.commitCount } : {}
82568
+ };
82569
+ const codeResponse = await fetchFn(device.code, {
82570
+ method: "POST",
82571
+ headers: { "content-type": "application/json", accept: "application/json" },
82572
+ body: JSON.stringify({ client_id: clientId, scope: "project:link", link })
82573
+ });
82574
+ if (!codeResponse.ok) {
82575
+ const text = (await codeResponse.text()).slice(0, 200);
82576
+ if (codeResponse.status === 404) {
82577
+ throw new Error("this console does not offer the link flow yet (device authorization is off)");
82578
+ }
82579
+ throw new Error(`link request failed: HTTP ${codeResponse.status} ${text}`);
82580
+ }
82581
+ const code = await codeResponse.json();
82582
+ if (!code.device_code || !code.user_code || !code.verification_uri || !code.expires_in) {
82583
+ throw new Error("link request returned an incomplete response");
82584
+ }
82585
+ opts.onVerification?.({
82586
+ url: code.verification_uri_complete ?? code.verification_uri,
82587
+ userCode: code.user_code,
82588
+ expiresInSec: code.expires_in
82589
+ });
82590
+ const deadline = Date.now() + Math.min(code.expires_in * 1e3, opts.timeoutMs ?? Number.POSITIVE_INFINITY);
82591
+ let intervalMs = Math.max(2, code.interval ?? 5) * 1e3;
82592
+ const sleep = opts.sleep ?? ((ms) => new Promise((resolve7) => setTimeout(resolve7, ms)));
82593
+ while (Date.now() < deadline) {
82594
+ await sleep(intervalMs);
82595
+ const poll = await fetchFn(device.token, {
82596
+ method: "POST",
82597
+ headers: { "content-type": "application/json", accept: "application/json" },
82598
+ body: JSON.stringify({
82599
+ grant_type: "urn:ietf:params:oauth:grant-type:device_code",
82600
+ device_code: code.device_code,
82601
+ client_id: clientId
82602
+ })
82603
+ });
82604
+ const payload = await poll.json().catch(() => null);
82605
+ if (!poll.ok) {
82606
+ const error48 = typeof payload?.["error"] === "string" ? payload["error"] : "device_poll_failed";
82607
+ if (error48 === "authorization_pending") continue;
82608
+ if (error48 === "slow_down") {
82609
+ intervalMs += 5e3;
82610
+ continue;
82611
+ }
82612
+ if (error48 === "access_denied") return { status: "denied" };
82613
+ if (error48 === "expired_token") return { status: "expired" };
82614
+ throw new Error(`link approval failed: ${error48}`);
82615
+ }
82616
+ const decision = payload?.["link"];
82617
+ if (!isDecision(decision)) {
82618
+ throw new Error("the console approved the device but returned no link decision");
82619
+ }
82620
+ return {
82621
+ status: "approved",
82622
+ decision,
82623
+ installationId: typeof payload?.["installation_id"] === "string" ? payload["installation_id"] : null
82624
+ };
82625
+ }
82626
+ return { status: "expired" };
82627
+ }
82628
+ function collectRepoFacts(root) {
82629
+ const git3 = (args2) => {
82630
+ try {
82631
+ return execFileSync4("git", args2, { cwd: root, encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] }).trim();
82632
+ } catch {
82633
+ return null;
82634
+ }
82635
+ };
82636
+ const roots = git3(["rev-list", "--max-parents=0", "HEAD"]);
82637
+ const count = git3(["rev-list", "--count", "HEAD"]);
82638
+ const rootCommitSha = roots?.split(/\s+/).filter(Boolean).pop();
82639
+ const commitCount = count && /^\d+$/.test(count) ? Number(count) : void 0;
82640
+ return {
82641
+ ...rootCommitSha && /^[0-9a-f]{40,64}$/i.test(rootCommitSha) ? { rootCommitSha } : {},
82642
+ ...typeof commitCount === "number" ? { commitCount } : {}
82643
+ };
82644
+ }
82645
+ var IGNORE_STARTER = `# .errataignore \u2014 the hard control over what errata may read or send.
82646
+ # Paths, content keywords and tags listed here are dropped from hunks and
82647
+ # narrative before anything leaves this machine, and an ignored path yields no
82648
+ # symbol anchor at all. Scrubbing is best effort; this list is absolute.
82649
+ # A personal list can only add to the organization's list, never weaken it.
82650
+
82651
+ # secrets and environment
82652
+ .env
82653
+ .env.*
82654
+ *.pem
82655
+ *.key
82656
+ *.p12
82657
+ secrets/
82658
+ credentials*
82659
+
82660
+ # fixtures and captured data
82661
+ fixtures/
82662
+ **/testdata/
82663
+ *.dump
82664
+ *.sql.gz
82665
+ `;
82666
+ function applyConsents(profile, config2, c, decidedAt, now = Date.now) {
82667
+ const nextConfig = {
82668
+ ...config2,
82669
+ consent: {
82670
+ ...config2.consent,
82671
+ sync: c.sync !== "off",
82672
+ telemetry: c.telemetry,
82673
+ contributePackages: c.packages && c.sync !== "off"
82674
+ },
82675
+ onboardedAt: config2.onboardedAt ?? now()
82676
+ };
82677
+ const nextProfile = {
82678
+ ...profile,
82679
+ syncConsent: c.sync === "automatic" ? "automatic" : c.sync === "ask" ? "ask-each-time" : "disabled",
82680
+ linkConsents: {
82681
+ structure: c.structure,
82682
+ mineHistory: c.mineHistory,
82683
+ contributeHistory: c.contributeHistory && c.mineHistory && c.sync !== "off",
82684
+ decidedAt
82685
+ }
82686
+ };
82687
+ const prev = profile.linkConsents;
82688
+ const changed = profile.syncConsent !== nextProfile.syncConsent || config2.consent.sync !== nextConfig.consent.sync || config2.consent.telemetry !== nextConfig.consent.telemetry || config2.consent.contributePackages !== nextConfig.consent.contributePackages || !prev || prev.structure !== nextProfile.linkConsents.structure || prev.mineHistory !== nextProfile.linkConsents.mineHistory || prev.contributeHistory !== nextProfile.linkConsents.contributeHistory;
82689
+ return { profile: nextProfile, config: nextConfig, changed };
82690
+ }
82691
+ function applyLinkDecision(root, profile, config2, decision, now = Date.now) {
82692
+ const applied = applyConsents(profile, config2, decision.consents, decision.approvedAt, now);
82693
+ const nextConfig = applied.config;
82694
+ const nextProfile = {
82695
+ ...applied.profile,
82696
+ projectId: decision.projectId,
82697
+ projectLocator: decision.locator
82698
+ };
82699
+ delete nextProfile.projectLinkDisabled;
82700
+ let wroteIgnore = false;
82701
+ if (decision.writeIgnoreStarter !== false) {
82702
+ const target = join34(root, ".errataignore");
82703
+ if (!existsSync29(target)) {
82704
+ writeFileSync22(target, IGNORE_STARTER, "utf8");
82705
+ wroteIgnore = true;
82706
+ }
82707
+ }
82708
+ return { profile: nextProfile, config: nextConfig, wroteIgnore };
82709
+ }
82710
+
82316
82711
  // src/multi.ts
82317
82712
  init_registry();
82318
82713
 
82319
82714
  // src/adopt.ts
82320
82715
  init_git_remote();
82321
- import { existsSync as existsSync29 } from "node:fs";
82322
- import { dirname as dirname12, join as join34 } from "node:path";
82716
+ import { existsSync as existsSync30 } from "node:fs";
82717
+ import { dirname as dirname12, join as join35 } from "node:path";
82323
82718
  function findGitRoot(absPath) {
82324
82719
  let dir = absPath;
82325
82720
  for (let depth = 0; depth < 64; depth++) {
82326
- if (existsSync29(join34(dir, ".git"))) return dir;
82721
+ if (existsSync30(join35(dir, ".git"))) return dir;
82327
82722
  const parent = dirname12(dir);
82328
82723
  if (parent === dir) return null;
82329
82724
  dir = parent;
@@ -82843,7 +83238,7 @@ async function startMultiDaemon(opts = {}) {
82843
83238
  records.push(rec);
82844
83239
  void ambientLinkAll();
82845
83240
  try {
82846
- writeFileSync22(
83241
+ writeFileSync23(
82847
83242
  rec.engine.paths.daemonLock,
82848
83243
  JSON.stringify({ pid: process.pid, webUiUrl: `${baseUrl}/ws/${rec.id}`, startedAt: Date.now() }),
82849
83244
  "utf8"
@@ -83056,7 +83451,7 @@ async function startMultiDaemon(opts = {}) {
83056
83451
  lastActiveAt = Math.max(lastActiveAt, rec.engine.lastActivityAt());
83057
83452
  } catch {
83058
83453
  }
83059
- const reason = retireDecision({ exists: existsSync30(rec.root), lastActiveAt, now });
83454
+ const reason = retireDecision({ exists: existsSync31(rec.root), lastActiveAt, now });
83060
83455
  if (reason) void retireWorkspace(rec, reason);
83061
83456
  }
83062
83457
  };
@@ -83124,7 +83519,7 @@ async function startMultiDaemon(opts = {}) {
83124
83519
  baseUrl = `http://127.0.0.1:${port}`;
83125
83520
  try {
83126
83521
  ensureDir(globalDir());
83127
- writeFileSync22(
83522
+ writeFileSync23(
83128
83523
  lockPath,
83129
83524
  JSON.stringify({ pid: process.pid, webUiUrl: baseUrl, startedAt: Date.now() }),
83130
83525
  "utf8"
@@ -83133,7 +83528,7 @@ async function startMultiDaemon(opts = {}) {
83133
83528
  }
83134
83529
  for (const r of records) {
83135
83530
  try {
83136
- writeFileSync22(
83531
+ writeFileSync23(
83137
83532
  r.engine.paths.daemonLock,
83138
83533
  JSON.stringify({ pid: process.pid, webUiUrl: `${baseUrl}/ws/${r.id}`, startedAt: Date.now() }),
83139
83534
  "utf8"
@@ -83209,7 +83604,7 @@ async function startMultiDaemon(opts = {}) {
83209
83604
  } else if (re.reason === "error") {
83210
83605
  console.warn(`[errata] project reanchor failed for ${r.engine.profile.name}: ${re.detail}`);
83211
83606
  }
83212
- if (r.engine.profile.linkConsents && r.engine.profile.projectHydratedAt && !historyInFlight.has(r.root)) {
83607
+ if (r.engine.profile.projectHydratedAt && !historyInFlight.has(r.root)) {
83213
83608
  historyInFlight.add(r.root);
83214
83609
  const engine = r.engine;
83215
83610
  const root = r.root;
@@ -83223,6 +83618,22 @@ async function startMultiDaemon(opts = {}) {
83223
83618
  };
83224
83619
  void startHistoryStage(root, engine.profile, client, {
83225
83620
  anchors: { index: anchorIndex, salt },
83621
+ // PS-web projects: the console's consents (Privacy tab) win over
83622
+ // the profile's memory; applied to config and profile here, with
83623
+ // the change printed so a flipped switch is visible in the log.
83624
+ applyConsents: (p, consents) => {
83625
+ const cfg2 = loadConfig();
83626
+ const applied = applyConsents(p, cfg2, consents, (/* @__PURE__ */ new Date()).toISOString());
83627
+ if (applied.changed) {
83628
+ saveConfig(applied.config);
83629
+ Object.assign(engine.profile, applied.profile);
83630
+ saveProfile(root, engine.profile);
83631
+ console.log(
83632
+ `[errata] consents applied from the console for ${engine.profile.name}: sync=${consents.sync} telemetry=${consents.telemetry ? "on" : "off"} packages=${consents.packages ? "on" : "off"} structure=${consents.structure ? "on" : "off"} history=${consents.mineHistory ? consents.contributeHistory ? "mine+contribute" : "mine" : "off"}`
83633
+ );
83634
+ }
83635
+ return applied.profile;
83636
+ },
83226
83637
  saveProfile: (p) => {
83227
83638
  Object.assign(engine.profile, p);
83228
83639
  saveProfile(root, engine.profile);
@@ -84008,392 +84419,6 @@ function errText(err2) {
84008
84419
  // src/cli.ts
84009
84420
  init_cloud_endpoint_policy();
84010
84421
 
84011
- // src/oauth-login.ts
84012
- init_src7();
84013
- init_cloud_auth();
84014
- import { createServer } from "node:http";
84015
- var DEFAULT_OAUTH_SCOPE = "openid profile graph:read graph:write mcp:tools";
84016
- var strip = (u) => u.replace(/\/+$/, "");
84017
- function deviceEndpoints(tokenEndpoint) {
84018
- const token = new URL(tokenEndpoint);
84019
- const authRoot = token.pathname.replace(/\/(?:oauth2|mcp)\/token\/?$/, "");
84020
- if (authRoot === token.pathname) throw new Error("authorization server exposes no device endpoint");
84021
- const prefix = authRoot.replace(/\/api\/auth\/?$/, "");
84022
- const at = (path2) => new URL(`${path2}`, `${token.origin}${prefix || "/"}`).toString();
84023
- return {
84024
- code: at(`${prefix}/api/auth/device/code`),
84025
- token: at(`${prefix}/api/auth/device/token`),
84026
- resource: at(`${prefix}/api/gate/session-token`)
84027
- };
84028
- }
84029
- async function loginOAuthDevice(opts) {
84030
- const fetchFn = opts.fetchFn ?? fetch;
84031
- const endpoints = await discoverEndpoints(opts.cloudUrl, fetchFn);
84032
- const device = deviceEndpoints(endpoints.tokenEndpoint);
84033
- const clientId = oauthClientId();
84034
- const scope = opts.scope ?? DEFAULT_OAUTH_SCOPE;
84035
- const codeResponse = await fetchFn(device.code, {
84036
- method: "POST",
84037
- headers: { "content-type": "application/json", accept: "application/json" },
84038
- body: JSON.stringify({ client_id: clientId, scope })
84039
- });
84040
- if (!codeResponse.ok) {
84041
- throw new Error(`device authorization failed: HTTP ${codeResponse.status} ${(await codeResponse.text()).slice(0, 200)}`);
84042
- }
84043
- const code = await codeResponse.json();
84044
- if (!code.device_code || !code.user_code || !code.verification_uri || !code.expires_in) {
84045
- throw new Error("device authorization returned an incomplete response");
84046
- }
84047
- opts.onVerification?.({
84048
- url: code.verification_uri_complete ?? code.verification_uri,
84049
- userCode: code.user_code,
84050
- expiresInSec: code.expires_in
84051
- });
84052
- const deadline = Date.now() + Math.min(code.expires_in * 1e3, opts.timeoutMs ?? Number.POSITIVE_INFINITY);
84053
- let intervalMs = Math.max(2, code.interval ?? 5) * 1e3;
84054
- let deviceSession = null;
84055
- let approvedInstallation = null;
84056
- while (Date.now() < deadline) {
84057
- await (opts.sleep ?? ((ms) => new Promise((resolve7) => setTimeout(resolve7, ms))))(intervalMs);
84058
- const poll = await fetchFn(device.token, {
84059
- method: "POST",
84060
- headers: { "content-type": "application/json", accept: "application/json" },
84061
- body: JSON.stringify({
84062
- grant_type: "urn:ietf:params:oauth:grant-type:device_code",
84063
- device_code: code.device_code,
84064
- client_id: clientId
84065
- })
84066
- });
84067
- const payload = await poll.json().catch(() => null);
84068
- if (!poll.ok) {
84069
- const error48 = typeof payload?.["error"] === "string" ? payload["error"] : "device_poll_failed";
84070
- if (error48 === "authorization_pending") continue;
84071
- if (error48 === "slow_down") {
84072
- intervalMs += 5e3;
84073
- continue;
84074
- }
84075
- throw new Error(`device authorization failed: ${error48}`);
84076
- }
84077
- deviceSession = typeof payload?.["access_token"] === "string" ? payload["access_token"] : null;
84078
- approvedInstallation = typeof payload?.["installation_id"] === "string" ? payload["installation_id"] : null;
84079
- break;
84080
- }
84081
- if (!deviceSession || !approvedInstallation) throw new Error("device authorization expired");
84082
- if (approvedInstallation !== opts.installationId) {
84083
- throw new Error("approved installation does not match the terminal request");
84084
- }
84085
- const resourceResponse = await fetchFn(device.resource, {
84086
- method: "POST",
84087
- headers: {
84088
- authorization: `Bearer ${deviceSession}`,
84089
- "content-type": "application/json",
84090
- accept: "application/json"
84091
- },
84092
- body: JSON.stringify({
84093
- installationId: approvedInstallation,
84094
- clientId,
84095
- scopes: scope.split(/\s+/).filter((value) => !["openid", "profile", "email", "offline_access"].includes(value))
84096
- })
84097
- });
84098
- const resource = await resourceResponse.json().catch(() => null);
84099
- if (!resourceResponse.ok || typeof resource?.["access_token"] !== "string") {
84100
- throw new Error(`device resource exchange failed: HTTP ${resourceResponse.status}`);
84101
- }
84102
- return {
84103
- installationId: approvedInstallation,
84104
- clientId,
84105
- tokenEndpoint: device.resource,
84106
- tokens: {
84107
- accessToken: resource["access_token"],
84108
- refreshToken: deviceSession,
84109
- ...typeof resource["expires_in"] === "number" ? { expiresInSec: resource["expires_in"] } : {}
84110
- }
84111
- };
84112
- }
84113
- async function discoverEndpoints(cloudUrl, fetchFn = fetch) {
84114
- let consoleUrl = cloudUrl;
84115
- try {
84116
- const prm = await fetchFn(`${strip(cloudUrl)}/.well-known/oauth-protected-resource`);
84117
- if (prm.ok) {
84118
- const j = await prm.json();
84119
- if (j.authorization_servers?.[0]) consoleUrl = j.authorization_servers[0];
84120
- }
84121
- } catch {
84122
- }
84123
- try {
84124
- const asm = await fetchFn(`${strip(consoleUrl)}/.well-known/oauth-authorization-server`);
84125
- if (asm.ok) {
84126
- const j = await asm.json();
84127
- if (j.authorization_endpoint && j.token_endpoint) {
84128
- return {
84129
- authorizationEndpoint: j.authorization_endpoint,
84130
- tokenEndpoint: j.token_endpoint,
84131
- ...j.registration_endpoint ? { registrationEndpoint: j.registration_endpoint } : {}
84132
- };
84133
- }
84134
- }
84135
- } catch {
84136
- }
84137
- const base = strip(consoleUrl);
84138
- return {
84139
- authorizationEndpoint: `${base}/api/auth/oauth2/authorize`,
84140
- tokenEndpoint: `${base}/api/auth/oauth2/token`
84141
- };
84142
- }
84143
- function buildAuthorizeUrl(endpoint, p) {
84144
- const u = new URL(endpoint);
84145
- u.searchParams.set("response_type", "code");
84146
- u.searchParams.set("client_id", p.clientId);
84147
- u.searchParams.set("redirect_uri", p.redirectUri);
84148
- u.searchParams.set("scope", p.scope);
84149
- u.searchParams.set("state", p.state);
84150
- u.searchParams.set("code_challenge", p.challenge);
84151
- u.searchParams.set("code_challenge_method", p.method);
84152
- return u.toString();
84153
- }
84154
- async function loginOAuthLoopback(opts) {
84155
- const fetchFn = opts.fetchFn ?? fetch;
84156
- const endpoints = await discoverEndpoints(opts.cloudUrl, fetchFn);
84157
- const pkce = generatePkce();
84158
- const state = randomState();
84159
- const scope = opts.scope ?? DEFAULT_OAUTH_SCOPE;
84160
- const { code, redirectUri, clientId } = await new Promise(
84161
- (resolve7, reject) => {
84162
- const server = createServer((req, res) => {
84163
- const u = new URL(req.url ?? "/", "http://127.0.0.1");
84164
- if (u.pathname !== "/callback") {
84165
- res.writeHead(404);
84166
- res.end();
84167
- return;
84168
- }
84169
- const addr2 = server.address();
84170
- const port = typeof addr2 === "object" && addr2 ? addr2.port : 0;
84171
- const err2 = u.searchParams.get("error");
84172
- const code2 = u.searchParams.get("code");
84173
- const returnedState = u.searchParams.get("state");
84174
- if (err2 || !code2 || returnedState !== state) {
84175
- res.writeHead(400, { "content-type": "text/plain" });
84176
- res.end(`login failed: ${err2 ?? (returnedState !== state ? "state_mismatch" : "no_code")}`);
84177
- server.close();
84178
- reject(new Error(err2 ?? "invalid_callback"));
84179
- return;
84180
- }
84181
- res.writeHead(200, { "content-type": "text/html" });
84182
- res.end("<html><body>inErrata: you're logged in. You can close this tab.</body></html>");
84183
- server.close();
84184
- resolve7({ code: code2, redirectUri: `http://127.0.0.1:${port}/callback`, clientId: activeClientId });
84185
- });
84186
- let activeClientId = oauthClientId();
84187
- server.on("error", reject);
84188
- server.listen(0, "127.0.0.1", () => {
84189
- void (async () => {
84190
- try {
84191
- const addr2 = server.address();
84192
- const port = typeof addr2 === "object" && addr2 ? addr2.port : 0;
84193
- const redirectUri2 = `http://127.0.0.1:${port}/callback`;
84194
- if (endpoints.registrationEndpoint) {
84195
- const registered = await registerOAuthClient(fetchFn, endpoints.registrationEndpoint, {
84196
- redirectUri: redirectUri2,
84197
- scope,
84198
- clientName: "errata daemon"
84199
- });
84200
- activeClientId = registered.clientId;
84201
- }
84202
- const url2 = buildAuthorizeUrl(endpoints.authorizationEndpoint, {
84203
- clientId: activeClientId,
84204
- redirectUri: redirectUri2,
84205
- scope,
84206
- state,
84207
- challenge: pkce.challenge,
84208
- method: pkce.method
84209
- });
84210
- if (opts.open) opts.open(url2);
84211
- else console.log(`approve this login in your browser:
84212
- ${url2}`);
84213
- } catch (err2) {
84214
- server.close();
84215
- reject(err2);
84216
- }
84217
- })();
84218
- });
84219
- if (opts.timeoutMs) {
84220
- setTimeout(() => {
84221
- server.close();
84222
- reject(new Error("login timed out \u2014 run `errata login` again"));
84223
- }, opts.timeoutMs).unref?.();
84224
- }
84225
- }
84226
- );
84227
- const tokens = await exchangeAuthorizationCode(fetchFn, endpoints.tokenEndpoint, {
84228
- code,
84229
- codeVerifier: pkce.verifier,
84230
- redirectUri,
84231
- clientId
84232
- });
84233
- return { tokens, tokenEndpoint: endpoints.tokenEndpoint, clientId };
84234
- }
84235
-
84236
- // src/link-flow.ts
84237
- init_cloud_auth();
84238
- import { createHash as createHash15 } from "node:crypto";
84239
- import { execFileSync as execFileSync4 } from "node:child_process";
84240
- import { existsSync as existsSync31, writeFileSync as writeFileSync23 } from "node:fs";
84241
- import { join as join35 } from "node:path";
84242
- function remoteFingerprint(locator) {
84243
- return createHash15("sha256").update(locator).digest("hex");
84244
- }
84245
- function isDecision(value) {
84246
- if (!value || typeof value !== "object") return false;
84247
- const v = value;
84248
- return typeof v["projectId"] === "string" && typeof v["locator"] === "string" && (v["visibility"] === "public" || v["visibility"] === "private") && typeof v["consents"] === "object" && v["consents"] !== null;
84249
- }
84250
- async function linkThroughConsole(opts) {
84251
- const fetchFn = opts.fetchFn ?? fetch;
84252
- const endpoints = await discoverEndpoints(opts.cloudUrl, fetchFn);
84253
- const device = deviceEndpoints(endpoints.tokenEndpoint);
84254
- const clientId = oauthClientId();
84255
- const link = {
84256
- locator: opts.facts.locator,
84257
- remoteFingerprint: remoteFingerprint(opts.facts.locator),
84258
- ...opts.facts.name ? { name: opts.facts.name } : {},
84259
- ...opts.facts.rootCommitSha ? { rootCommitSha: opts.facts.rootCommitSha } : {},
84260
- ...opts.facts.installationId ? { installationId: opts.facts.installationId } : {},
84261
- ...opts.facts.agents && opts.facts.agents.length > 0 ? { agents: opts.facts.agents.slice(0, 12) } : {},
84262
- ...typeof opts.facts.commitCount === "number" ? { commitCount: opts.facts.commitCount } : {}
84263
- };
84264
- const codeResponse = await fetchFn(device.code, {
84265
- method: "POST",
84266
- headers: { "content-type": "application/json", accept: "application/json" },
84267
- body: JSON.stringify({ client_id: clientId, scope: "project:link", link })
84268
- });
84269
- if (!codeResponse.ok) {
84270
- const text = (await codeResponse.text()).slice(0, 200);
84271
- if (codeResponse.status === 404) {
84272
- throw new Error("this console does not offer the link flow yet (device authorization is off)");
84273
- }
84274
- throw new Error(`link request failed: HTTP ${codeResponse.status} ${text}`);
84275
- }
84276
- const code = await codeResponse.json();
84277
- if (!code.device_code || !code.user_code || !code.verification_uri || !code.expires_in) {
84278
- throw new Error("link request returned an incomplete response");
84279
- }
84280
- opts.onVerification?.({
84281
- url: code.verification_uri_complete ?? code.verification_uri,
84282
- userCode: code.user_code,
84283
- expiresInSec: code.expires_in
84284
- });
84285
- const deadline = Date.now() + Math.min(code.expires_in * 1e3, opts.timeoutMs ?? Number.POSITIVE_INFINITY);
84286
- let intervalMs = Math.max(2, code.interval ?? 5) * 1e3;
84287
- const sleep = opts.sleep ?? ((ms) => new Promise((resolve7) => setTimeout(resolve7, ms)));
84288
- while (Date.now() < deadline) {
84289
- await sleep(intervalMs);
84290
- const poll = await fetchFn(device.token, {
84291
- method: "POST",
84292
- headers: { "content-type": "application/json", accept: "application/json" },
84293
- body: JSON.stringify({
84294
- grant_type: "urn:ietf:params:oauth:grant-type:device_code",
84295
- device_code: code.device_code,
84296
- client_id: clientId
84297
- })
84298
- });
84299
- const payload = await poll.json().catch(() => null);
84300
- if (!poll.ok) {
84301
- const error48 = typeof payload?.["error"] === "string" ? payload["error"] : "device_poll_failed";
84302
- if (error48 === "authorization_pending") continue;
84303
- if (error48 === "slow_down") {
84304
- intervalMs += 5e3;
84305
- continue;
84306
- }
84307
- if (error48 === "access_denied") return { status: "denied" };
84308
- if (error48 === "expired_token") return { status: "expired" };
84309
- throw new Error(`link approval failed: ${error48}`);
84310
- }
84311
- const decision = payload?.["link"];
84312
- if (!isDecision(decision)) {
84313
- throw new Error("the console approved the device but returned no link decision");
84314
- }
84315
- return {
84316
- status: "approved",
84317
- decision,
84318
- installationId: typeof payload?.["installation_id"] === "string" ? payload["installation_id"] : null
84319
- };
84320
- }
84321
- return { status: "expired" };
84322
- }
84323
- function collectRepoFacts(root) {
84324
- const git3 = (args2) => {
84325
- try {
84326
- return execFileSync4("git", args2, { cwd: root, encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] }).trim();
84327
- } catch {
84328
- return null;
84329
- }
84330
- };
84331
- const roots = git3(["rev-list", "--max-parents=0", "HEAD"]);
84332
- const count = git3(["rev-list", "--count", "HEAD"]);
84333
- const rootCommitSha = roots?.split(/\s+/).filter(Boolean).pop();
84334
- const commitCount = count && /^\d+$/.test(count) ? Number(count) : void 0;
84335
- return {
84336
- ...rootCommitSha && /^[0-9a-f]{40,64}$/i.test(rootCommitSha) ? { rootCommitSha } : {},
84337
- ...typeof commitCount === "number" ? { commitCount } : {}
84338
- };
84339
- }
84340
- var IGNORE_STARTER = `# .errataignore \u2014 the hard control over what errata may read or send.
84341
- # Paths, content keywords and tags listed here are dropped from hunks and
84342
- # narrative before anything leaves this machine, and an ignored path yields no
84343
- # symbol anchor at all. Scrubbing is best effort; this list is absolute.
84344
- # A personal list can only add to the organization's list, never weaken it.
84345
-
84346
- # secrets and environment
84347
- .env
84348
- .env.*
84349
- *.pem
84350
- *.key
84351
- *.p12
84352
- secrets/
84353
- credentials*
84354
-
84355
- # fixtures and captured data
84356
- fixtures/
84357
- **/testdata/
84358
- *.dump
84359
- *.sql.gz
84360
- `;
84361
- function applyLinkDecision(root, profile, config2, decision, now = Date.now) {
84362
- const c = decision.consents;
84363
- const nextConfig = {
84364
- ...config2,
84365
- consent: {
84366
- ...config2.consent,
84367
- sync: c.sync !== "off",
84368
- telemetry: c.telemetry,
84369
- contributePackages: c.packages && c.sync !== "off"
84370
- },
84371
- onboardedAt: config2.onboardedAt ?? now()
84372
- };
84373
- const nextProfile = {
84374
- ...profile,
84375
- syncConsent: c.sync === "automatic" ? "automatic" : c.sync === "ask" ? "ask-each-time" : "disabled",
84376
- projectId: decision.projectId,
84377
- projectLocator: decision.locator,
84378
- linkConsents: {
84379
- structure: c.structure,
84380
- mineHistory: c.mineHistory,
84381
- contributeHistory: c.contributeHistory && c.sync !== "off",
84382
- decidedAt: decision.approvedAt
84383
- }
84384
- };
84385
- delete nextProfile.projectLinkDisabled;
84386
- let wroteIgnore = false;
84387
- if (decision.writeIgnoreStarter !== false) {
84388
- const target = join35(root, ".errataignore");
84389
- if (!existsSync31(target)) {
84390
- writeFileSync23(target, IGNORE_STARTER, "utf8");
84391
- wroteIgnore = true;
84392
- }
84393
- }
84394
- return { profile: nextProfile, config: nextConfig, wroteIgnore };
84395
- }
84396
-
84397
84422
  // src/device-login.ts
84398
84423
  init_src7();
84399
84424
  var DEFAULT_SCOPE = "openid profile graph:read graph:write mcp:tools";
@@ -86076,6 +86101,13 @@ async function cmdLink(args2) {
86076
86101
  }
86077
86102
  const consents = historyConsents(applied.profile);
86078
86103
  const reported = await reportHistoryStage(authedCloudClient(applied.config), applied.profile, (line) => console.warn(line));
86104
+ if (reported?.consents) {
86105
+ const synced = applyConsents(applied.profile, applied.config, reported.consents, (/* @__PURE__ */ new Date()).toISOString());
86106
+ if (synced.changed) {
86107
+ saveProfile(ROOT, synced.profile);
86108
+ saveConfig(synced.config);
86109
+ }
86110
+ }
86079
86111
  if (!consents.mineHistory) console.log("history: not mined (consent off)");
86080
86112
  else if (reported?.stopRequested) console.log("history: a stop request is standing in the console; the daemon honours it");
86081
86113
  else console.log(`history: consents recorded${consents.contributeHistory ? "" : " (mine only)"} \xB7 the daemon walks the repo on its next pass`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@inerrata-corporation/errata",
3
- "version": "2.0.6-dev.1989",
3
+ "version": "2.0.6-dev.2028",
4
4
  "description": "errata - local-first observation engine for AI coding agents",
5
5
  "type": "module",
6
6
  "bin": {
package/pass-worker.mjs CHANGED
@@ -15140,7 +15140,7 @@ var init_symbol_intent = __esm({
15140
15140
  });
15141
15141
 
15142
15142
  // ../../packages/shared/src/wire.ts
15143
- var CLOUD_NODE_LABELS, CLOUD_WRITABLE_NODE_LABELS, PROJECT_ONLY_NODE_LABELS, PROJECT_ONLY_NODE_LABEL_SET, PROJECT_ONLY_EDGE_TYPES, PROJECT_ONLY_EDGE_TYPE_SET, CLOUD_WRITABLE_NODE_LABEL_SET, CLOUD_EDGE_TYPES, INGEST_SOURCES, INGEST_EXTRACTION_SOURCES, CastaliaIngestNodeSchema, RouteContextCountWireSchema, CastaliaIngestEdgeSchema, SymbolSummarySidecarSchema, CastaliaIngestPayloadSchema;
15143
+ var CLOUD_NODE_LABELS, CLOUD_WRITABLE_NODE_LABELS, PROJECT_ONLY_NODE_LABELS, PROJECT_ONLY_NODE_LABEL_SET, PROJECT_ONLY_EDGE_TYPES, PROJECT_ONLY_EDGE_TYPE_SET, CLOUD_WRITABLE_NODE_LABEL_SET, CLOUD_EDGE_TYPES, INGEST_SOURCES, INGEST_EXTRACTION_SOURCES, CastaliaIngestNodeSchema, RouteContextCountWireSchema, ANCHOR_COMMIT_SHA_RE, ANCHOR_BODY_HASH_RE, CastaliaIngestEdgeSchema, SymbolSummarySidecarSchema, CastaliaIngestPayloadSchema;
15144
15144
  var init_wire = __esm({
15145
15145
  "../../packages/shared/src/wire.ts"() {
15146
15146
  "use strict";
@@ -15241,6 +15241,8 @@ var init_wire = __esm({
15241
15241
  independent: external_exports.number().int().min(0).optional(),
15242
15242
  inferredIndependent: external_exports.number().int().min(0).optional()
15243
15243
  });
15244
+ ANCHOR_COMMIT_SHA_RE = /^[0-9a-f]{7,40}$/;
15245
+ ANCHOR_BODY_HASH_RE = /^[0-9a-f]{8,64}$/;
15244
15246
  CastaliaIngestEdgeSchema = external_exports.object({
15245
15247
  fromCanonicalId: external_exports.string().min(6).max(160),
15246
15248
  toCanonicalId: external_exports.string().min(6).max(160),
@@ -15259,8 +15261,8 @@ var init_wire = __esm({
15259
15261
  * (reanchor) compares the hash with its local symbol's `bodyHash` to
15260
15262
  * classify the anchor current / pre-change; absent ⇒ the pre-versioning
15261
15263
  * contract, unverified. Only meaningful on `ANCHORED_AT`. */
15262
- observedAtCommit: external_exports.string().regex(/^[0-9a-f]{7,40}$/).optional(),
15263
- symbolContentHash: external_exports.string().regex(/^[0-9a-f]{8,64}$/).optional()
15264
+ observedAtCommit: external_exports.string().regex(ANCHOR_COMMIT_SHA_RE).optional(),
15265
+ symbolContentHash: external_exports.string().regex(ANCHOR_BODY_HASH_RE).optional()
15264
15266
  }).optional()
15265
15267
  });
15266
15268
  SymbolSummarySidecarSchema = external_exports.record(