@inerrata-corporation/errata 2.0.8-dev.2839 → 2.0.8-dev.2849

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 +67 -37
  2. package/package.json +1 -1
package/errata.mjs CHANGED
@@ -79931,7 +79931,7 @@ function createWatchBreaker(deps) {
79931
79931
  }
79932
79932
 
79933
79933
  // src/engine.ts
79934
- var DAEMON_VERSION = true ? "2.0.8-dev.2839" : "2.0.0-alpha.0";
79934
+ var DAEMON_VERSION = true ? "2.0.8-dev.2849" : "2.0.0-alpha.0";
79935
79935
  var IGNORED_PATH = /[\\/](?:\.git|node_modules|\.errata|\.claude|\.codex|\.cursor|\.turbo|\.next|dist|coverage|test-results|playwright-report|__pycache__)(?:[\\/]|$)/;
79936
79936
  var IGNORED_NOISE = /castalia\.db|eventlog\.sqlite|turn-cursors/;
79937
79937
  var GIT_OP_MUTE_MS = 4e3;
@@ -83728,6 +83728,22 @@ function isClaimedElsewhere(err2) {
83728
83728
  const e = err2;
83729
83729
  return e.status === 409 && e.code === "claimed_elsewhere";
83730
83730
  }
83731
+ function claimHoldStands(onDisk) {
83732
+ return Boolean(onDisk && !onDisk.projectId && onDisk.projectLinkHold);
83733
+ }
83734
+ var LINK_FIELDS = [
83735
+ "projectId",
83736
+ "projectLocator",
83737
+ "projectLinkHold",
83738
+ "projectLinkDisabled",
83739
+ "rootCommitReportedFor"
83740
+ ];
83741
+ function adoptLinkFields(into, from) {
83742
+ for (const k of LINK_FIELDS) {
83743
+ if (from[k] === void 0) delete into[k];
83744
+ else into[k] = from[k];
83745
+ }
83746
+ }
83731
83747
  async function readRootCommit(root) {
83732
83748
  try {
83733
83749
  const { stdout } = await execFileAsync("git", ["rev-list", "--max-parents=0", "HEAD"], {
@@ -85397,6 +85413,7 @@ function applyLinkDecision(root, profile, config2, decision, now = Date.now) {
85397
85413
  projectLocator: decision.locator
85398
85414
  };
85399
85415
  delete nextProfile.projectLinkDisabled;
85416
+ delete nextProfile.projectLinkHold;
85400
85417
  let wroteIgnore = false;
85401
85418
  if (decision.writeIgnoreStarter !== false) {
85402
85419
  const target = join37(root, ".errataignore");
@@ -85793,9 +85810,9 @@ async function startMultiDaemon(opts = {}) {
85793
85810
  if (identityBlocks.delete(wsId)) console.log(`[identity] workspace unblocked \u2014 spooled capture drains on this pass`);
85794
85811
  };
85795
85812
  const projectHolds = /* @__PURE__ */ new Map();
85796
- const noteProjectHold = (wsId, wsName, reason) => {
85813
+ const noteProjectHold = (wsId, wsName, reason, claimRoot) => {
85797
85814
  if (!projectHolds.has(wsId)) {
85798
- projectHolds.set(wsId, { workspace: wsName, reason, since: Date.now() });
85815
+ projectHolds.set(wsId, { workspace: wsName, reason, since: Date.now(), ...claimRoot ? { claimRoot } : {} });
85799
85816
  console.error(
85800
85817
  `[project] HOLDING cloud push for workspace "${wsName}": cannot prove its project \u2014 ${reason}. Shipping without the assertion would publish project-scoped knowledge to the whole org; the batch stays pending instead.`
85801
85818
  );
@@ -85804,6 +85821,14 @@ async function startMultiDaemon(opts = {}) {
85804
85821
  const clearProjectHold = (wsId) => {
85805
85822
  if (projectHolds.delete(wsId)) console.log(`[project] project assertion restored \u2014 held contributions drain on this pass`);
85806
85823
  };
85824
+ const releaseSettledClaimHolds = () => {
85825
+ for (const [wsId, hold] of projectHolds) {
85826
+ if (hold.claimRoot && !claimHoldStands(loadProfile(hold.claimRoot))) {
85827
+ projectHolds.delete(wsId);
85828
+ console.log(`[project] claim hold on "${hold.workspace}" settled by \`errata link\` \u2014 ships on the next pass`);
85829
+ }
85830
+ }
85831
+ };
85807
85832
  const cloudForIdentity = (identity) => {
85808
85833
  if (identity.invalid) return blockedCloudClient(identity.reason);
85809
85834
  if (identity.rule === "workspace-binding" && identity.profileName) {
@@ -85898,35 +85923,38 @@ async function startMultiDaemon(opts = {}) {
85898
85923
  if (detail) boundaryFlushDetail = detail;
85899
85924
  if (units > 0 || items > 0) boundaryFlushLastAdvanceAt = Date.now();
85900
85925
  };
85901
- const flushSnapshot = () => ({
85902
- running: boundaryFlushing,
85903
- stage: boundaryFlushStage,
85904
- detail: boundaryFlushDetail,
85905
- ageMs: boundaryFlushStartedAt ? Date.now() - boundaryFlushStartedAt : 0,
85906
- units: boundaryFlushUnits,
85907
- items: boundaryFlushItems,
85908
- chunks: boundaryFlushBatches,
85909
- stalledMs: boundaryFlushLastAdvanceAt ? Date.now() - boundaryFlushLastAdvanceAt : 0,
85910
- // Held workspaces, so a hold is REPORTABLE and not merely logged once at the
85911
- // transition. `errata status` reads this rather than the on-disk profile:
85912
- // the profile records what the workspace INTENDS to assert, which is why
85913
- // status could print "→ project 019f462d" for hours while every batch was
85914
- // being held or shipped unlinked. A hold is the daemon's actual behavior.
85915
- holds: [
85916
- ...[...projectHolds.values()].map((h) => ({
85917
- workspace: h.workspace,
85918
- kind: "project",
85919
- reason: h.reason,
85920
- sinceMs: Date.now() - h.since
85921
- })),
85922
- ...[...identityBlocks.values()].map((h) => ({
85923
- workspace: h.workspace,
85924
- kind: "identity",
85925
- reason: h.reason,
85926
- sinceMs: Date.now() - h.since
85927
- }))
85928
- ]
85929
- });
85926
+ const flushSnapshot = () => {
85927
+ releaseSettledClaimHolds();
85928
+ return {
85929
+ running: boundaryFlushing,
85930
+ stage: boundaryFlushStage,
85931
+ detail: boundaryFlushDetail,
85932
+ ageMs: boundaryFlushStartedAt ? Date.now() - boundaryFlushStartedAt : 0,
85933
+ units: boundaryFlushUnits,
85934
+ items: boundaryFlushItems,
85935
+ chunks: boundaryFlushBatches,
85936
+ stalledMs: boundaryFlushLastAdvanceAt ? Date.now() - boundaryFlushLastAdvanceAt : 0,
85937
+ // Held workspaces, so a hold is REPORTABLE and not merely logged once at the
85938
+ // transition. `errata status` reads this rather than the on-disk profile:
85939
+ // the profile records what the workspace INTENDS to assert, which is why
85940
+ // status could print "→ project 019f462d" for hours while every batch was
85941
+ // being held or shipped unlinked. A hold is the daemon's actual behavior.
85942
+ holds: [
85943
+ ...[...projectHolds.values()].map((h) => ({
85944
+ workspace: h.workspace,
85945
+ kind: "project",
85946
+ reason: h.reason,
85947
+ sinceMs: Date.now() - h.since
85948
+ })),
85949
+ ...[...identityBlocks.values()].map((h) => ({
85950
+ workspace: h.workspace,
85951
+ kind: "identity",
85952
+ reason: h.reason,
85953
+ sinceMs: Date.now() - h.since
85954
+ }))
85955
+ ]
85956
+ };
85957
+ };
85930
85958
  const endFlush = () => {
85931
85959
  boundaryFlushing = false;
85932
85960
  boundaryFlushStage = "";
@@ -86503,8 +86531,10 @@ async function startMultiDaemon(opts = {}) {
86503
86531
  if (!hasCloudCredential(c)) return;
86504
86532
  const client = cloudNow();
86505
86533
  for (const r of records) {
86506
- if (!effectiveConsent(c, r.engine.profile).sync) continue;
86507
- const out2 = await ensureProjectLink(r.root, r.engine.profile, client);
86534
+ const working = loadProfile(r.root) ?? r.engine.profile;
86535
+ if (!effectiveConsent(c, working).sync) continue;
86536
+ const out2 = await ensureProjectLink(r.root, working, client);
86537
+ if (working !== r.engine.profile) adoptLinkFields(r.engine.profile, working);
86508
86538
  if (out2.linked) {
86509
86539
  console.log(
86510
86540
  `[errata] linked ${r.engine.profile.name} \u2192 project ${out2.projectId}${out2.created ? " (created)" : ""}`
@@ -86512,7 +86542,7 @@ async function startMultiDaemon(opts = {}) {
86512
86542
  } else if (out2.reason === "error") {
86513
86543
  console.warn(`[errata] project link failed for ${r.engine.profile.name}: ${out2.detail}`);
86514
86544
  } else if (out2.reason === "held") {
86515
- noteProjectHold(r.id, r.engine.profile.name, out2.detail);
86545
+ noteProjectHold(r.id, r.engine.profile.name, out2.detail, r.root);
86516
86546
  }
86517
86547
  let primeSalt = null;
86518
86548
  if (r.engine.profile.projectId && !r.engine.profile.projectHydratedAt) {
@@ -86976,7 +87006,8 @@ async function startMultiDaemon(opts = {}) {
86976
87006
  noteProjectHold(
86977
87007
  r.id,
86978
87008
  r.entry.name,
86979
- "this repo is already on errata in another workspace \u2014 run `errata link` to join it or create your own project"
87009
+ "this repo is already on errata in another workspace \u2014 run `errata link` to join it or create your own project",
87010
+ r.root
86980
87011
  );
86981
87012
  continue;
86982
87013
  }
@@ -89389,7 +89420,6 @@ async function cmdLink(args2) {
89389
89420
  return;
89390
89421
  }
89391
89422
  delete profile.projectLinkDisabled;
89392
- delete profile.projectLinkHold;
89393
89423
  const remote = flag("--remote");
89394
89424
  if (remote) {
89395
89425
  const loc = detectRepoLocator(ROOT, remote);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@inerrata-corporation/errata",
3
- "version": "2.0.8-dev.2839",
3
+ "version": "2.0.8-dev.2849",
4
4
  "description": "errata - local-first observation engine for AI coding agents",
5
5
  "type": "module",
6
6
  "bin": {