ultra-builder-pro-cli 0.24.0 → 0.24.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -73,26 +73,31 @@ def inspect(root: Path) -> dict:
73
73
 
74
74
  envelope = context.get("envelope", {}) if context else {}
75
75
  diagnostics = envelope.get("diagnostics", {})
76
- baseline_codes = [
76
+ baseline_blockers = [
77
77
  str(item.get("code"))
78
78
  for item in [
79
79
  *diagnostics.get("needs_attention", []),
80
- *diagnostics.get("warnings", []),
81
80
  *diagnostics.get("hard_conflicts", []),
82
81
  ]
83
82
  if str(item.get("code", "")).startswith("BASELINE_")
84
83
  ]
84
+ baseline_warnings = [
85
+ str(item.get("code"))
86
+ for item in diagnostics.get("warnings", [])
87
+ if str(item.get("code", "")).startswith("BASELINE_")
88
+ ]
85
89
  baseline = envelope.get("baseline")
86
90
  baseline_is_ready = isinstance(baseline, dict) and baseline.get("status") == "ready"
87
91
  baseline_status = (
88
- "fail" if baseline_codes and baseline_is_ready
89
- else "warning" if baseline_codes
92
+ "fail" if baseline_blockers and baseline_is_ready
93
+ else "warning" if baseline_blockers or baseline_warnings
90
94
  else "pass"
91
95
  )
92
96
  report["checks"]["baseline"] = {
93
97
  "baseline": baseline,
94
- "blockers": baseline_codes,
95
- "readiness": "blocked" if baseline_codes else "ready",
98
+ "blockers": baseline_blockers,
99
+ "warnings": baseline_warnings,
100
+ "readiness": "blocked" if baseline_blockers else "ready",
96
101
  "status": baseline_status,
97
102
  }
98
103
  if baseline_status == "fail":
@@ -473,6 +473,13 @@ function invalidateConsumersFromEndpointInTx(db, start, {
473
473
  const key = endpointKey(endpoint);
474
474
  if (seen.has(key) || excluded.has(key) || key === endpointKey(start)) continue;
475
475
  seen.add(key);
476
+ if (endpoint.type === 'workflow') {
477
+ // Workflow rows are retained only as pre-v0.24 audit history. Preserve
478
+ // graph traversal for downstream current authorities, but never rewrite
479
+ // the historical workflow row or claim that it was invalidated.
480
+ queue.push(...outgoingEdges(db, endpoint));
481
+ continue;
482
+ }
476
483
  invalidated.push({ type: endpoint.type, id: endpoint.id });
477
484
  if (endpoint.type === 'artifact') {
478
485
  const row = db.prepare('SELECT status FROM artifacts WHERE id = ?').get(endpoint.id);
@@ -503,62 +510,6 @@ function invalidateConsumersFromEndpointInTx(db, start, {
503
510
  });
504
511
  }
505
512
  }
506
- if (endpoint.type === 'workflow') {
507
- const row = db.prepare(
508
- 'SELECT status, metadata_json, change_id, task_id FROM workflow_runs WHERE id = ?',
509
- ).get(endpoint.id);
510
- if (row) {
511
- const metadata = parseJson(row.metadata_json, {});
512
- if (['active', 'blocked', 'ready'].includes(row.status)) {
513
- delete metadata.authority_invalidation;
514
- metadata.draft_dirty = true;
515
- db.prepare(
516
- `UPDATE workflow_runs
517
- SET status = CASE WHEN status = 'ready' THEN 'active' ELSE status END,
518
- metadata_json = ?,
519
- updated_at = strftime('%Y-%m-%dT%H:%M:%fZ', 'now')
520
- WHERE id = ?`,
521
- ).run(JSON.stringify(metadata), endpoint.id);
522
- ops.appendEventInTx(db, {
523
- type: 'workflow_draft_changed',
524
- change_id: row.change_id,
525
- task_id: row.task_id,
526
- payload: {
527
- workflow_id: endpoint.id,
528
- source_type: start.type,
529
- source_id: start.id,
530
- reason,
531
- },
532
- });
533
- queue.push(...outgoingEdges(db, endpoint));
534
- continue;
535
- }
536
- if (metadata.authority_invalidation?.invalidated !== true) {
537
- metadata.authority_invalidation = {
538
- invalidated: true,
539
- source_type: start.type,
540
- source_id: start.id,
541
- reason,
542
- invalidated_at: new Date().toISOString(),
543
- };
544
- db.prepare(
545
- `UPDATE workflow_runs SET metadata_json = ?,
546
- updated_at = strftime('%Y-%m-%dT%H:%M:%fZ', 'now') WHERE id = ?`,
547
- ).run(JSON.stringify(metadata), endpoint.id);
548
- ops.appendEventInTx(db, {
549
- type: 'workflow_invalidated',
550
- change_id: row.change_id,
551
- task_id: row.task_id,
552
- payload: {
553
- workflow_id: endpoint.id,
554
- source_type: start.type,
555
- source_id: start.id,
556
- reason,
557
- },
558
- });
559
- }
560
- }
561
- }
562
513
  queue.push(...outgoingEdges(db, endpoint));
563
514
  }
564
515
  return invalidated;
@@ -660,27 +660,10 @@ function storedReadyInvariantBlockers(baseline) {
660
660
  if (!Array.isArray(baseline.scope) || baseline.scope.length === 0) {
661
661
  blockers.push('BASELINE_SCOPE_MISSING');
662
662
  }
663
- if (Array.isArray(baseline.spec_refs)) {
664
- for (const kind of ['discovery', 'product', 'architecture']) {
665
- if (!baseline.spec_refs.some((item) => item?.kind === kind)) {
666
- blockers.push(`BASELINE_SPEC_MISSING:${kind}`);
667
- }
668
- }
669
- }
670
- if (!Array.isArray(baseline.evidence) || baseline.evidence.length === 0) {
671
- blockers.push('BASELINE_EVIDENCE_MISSING');
672
- } else if (baseline.mode === 'brownfield'
673
- && !baseline.evidence.some((item) => item?.kind === 'source')) {
674
- blockers.push('BASELINE_SOURCE_EVIDENCE_MISSING');
675
- }
676
- if (!Array.isArray(baseline.verification) || baseline.verification.length === 0) {
677
- blockers.push('BASELINE_VERIFICATION_MISSING');
678
- } else {
663
+ if (Array.isArray(baseline.verification)) {
679
664
  for (const item of baseline.verification) {
680
665
  if (!item || typeof item !== 'object') {
681
666
  blockers.push('BASELINE_STATE_INVALID:verification');
682
- } else if (item.status === 'not_run') {
683
- blockers.push(`BASELINE_VERIFICATION_NOT_RUN:${item.name || 'unknown'}`);
684
667
  } else if (item.status === 'known_red') {
685
668
  if (!String(item.rationale || '').trim()) {
686
669
  blockers.push(`BASELINE_KNOWN_RED_RATIONALE_MISSING:${item.name || 'unknown'}`);
@@ -688,7 +671,7 @@ function storedReadyInvariantBlockers(baseline) {
688
671
  if (!baseline.known_red_accepted) {
689
672
  blockers.push(`BASELINE_KNOWN_RED_NOT_ACCEPTED:${item.name || 'unknown'}`);
690
673
  }
691
- } else if (item.status !== 'pass') {
674
+ } else if (!['pass', 'not_run'].includes(item.status)) {
692
675
  blockers.push(`BASELINE_VERIFICATION_STATUS_INVALID:${item.name || 'unknown'}`);
693
676
  }
694
677
  }
@@ -697,25 +680,54 @@ function storedReadyInvariantBlockers(baseline) {
697
680
  blockers.push('BASELINE_REVISION_MISSING');
698
681
  }
699
682
  if (!String(baseline.approved_by || '').trim()) blockers.push('BASELINE_APPROVER_MISSING');
700
- if (String(baseline.approval_note || '').trim().length < 3) {
683
+ if (!String(baseline.approval_note || '').trim()) {
701
684
  blockers.push('BASELINE_APPROVAL_NOTE_MISSING');
702
685
  }
703
686
  if (!baseline.converged_at || Number.isNaN(Date.parse(baseline.converged_at))) {
704
687
  blockers.push('BASELINE_CONVERGENCE_TIMESTAMP_MISSING');
705
688
  }
689
+ return [...new Set(blockers)];
690
+ }
691
+
692
+ function storedReadySemanticWarnings(baseline) {
693
+ const warnings = [];
694
+ if (Array.isArray(baseline.spec_refs)) {
695
+ for (const kind of ['discovery', 'product', 'architecture']) {
696
+ if (!baseline.spec_refs.some((item) => item?.kind === kind)) {
697
+ warnings.push(`BASELINE_SPEC_MISSING:${kind}`);
698
+ }
699
+ }
700
+ }
701
+ if (Array.isArray(baseline.evidence) && baseline.evidence.length === 0) {
702
+ warnings.push('BASELINE_EVIDENCE_MISSING');
703
+ }
704
+ if (baseline.mode === 'brownfield'
705
+ && Array.isArray(baseline.evidence)
706
+ && !baseline.evidence.some((item) => item?.kind === 'source')) {
707
+ warnings.push('BASELINE_SOURCE_EVIDENCE_MISSING');
708
+ }
709
+ if (Array.isArray(baseline.verification) && baseline.verification.length === 0) {
710
+ warnings.push('BASELINE_VERIFICATION_MISSING');
711
+ } else if (Array.isArray(baseline.verification)) {
712
+ for (const item of baseline.verification) {
713
+ if (item?.status === 'not_run') {
714
+ warnings.push(`BASELINE_VERIFICATION_NOT_RUN:${item.name || 'unknown'}`);
715
+ }
716
+ }
717
+ }
706
718
  if (Array.isArray(baseline.unknowns)) {
707
719
  for (const item of baseline.unknowns.filter((unknown) => unknown?.blocking === true)) {
708
- blockers.push(`BASELINE_UNKNOWN_BLOCKING:${item.summary || 'unknown'}`);
720
+ warnings.push(`BASELINE_UNKNOWN_RECORDED:${item.summary || 'unknown'}`);
709
721
  }
710
722
  }
711
723
  if (Array.isArray(baseline.gaps)) {
712
724
  for (const gap of baseline.gaps.filter((item) => (
713
725
  item?.status === 'open' && item?.blocking === true
714
726
  ))) {
715
- blockers.push(`BASELINE_GAP_BLOCKING:${gap.id || 'unknown'}`);
727
+ warnings.push(`BASELINE_GAP_RECORDED:${gap.id || 'unknown'}`);
716
728
  }
717
729
  }
718
- return [...new Set(blockers)];
730
+ return [...new Set(warnings)];
719
731
  }
720
732
 
721
733
  function acceptedBaselineResearch(db, baseline) {
@@ -736,7 +748,7 @@ function convergenceResearchBlockers(db, baseline) {
736
748
  function storedResearchHealth(db, baseline) {
737
749
  const checkpointId = baseline.research_checkpoint_id || baseline.research_run_id;
738
750
  if (!checkpointId) {
739
- return { blockers: ['BASELINE_RESEARCH_PROVENANCE_MISSING'], warnings: [] };
751
+ return { blockers: [], warnings: ['BASELINE_RESEARCH_PROVENANCE_MISSING'] };
740
752
  }
741
753
  let checkpoint;
742
754
  try {
@@ -751,10 +763,19 @@ function storedResearchHealth(db, baseline) {
751
763
  || checkpoint.stage !== 'research'
752
764
  || checkpoint.scope_type !== 'baseline'
753
765
  || checkpoint.scope_id !== baseline.id
754
- || checkpoint.status !== 'accepted'
755
766
  || checkpoints.checkpointDigest(checkpoint) !== checkpoint.digest) {
756
767
  return { blockers: ['BASELINE_RESEARCH_RECORD_INVALID'], warnings: [] };
757
768
  }
769
+ if (checkpoint.status !== 'accepted') {
770
+ return {
771
+ blockers: [],
772
+ warnings: [
773
+ checkpoint.status === 'superseded'
774
+ ? 'BASELINE_RESEARCH_SUPERSEDED'
775
+ : `BASELINE_RESEARCH_NOT_ACCEPTED:${checkpoint.status}`,
776
+ ],
777
+ };
778
+ }
758
779
  return { blockers: [], warnings: checkpoint.diagnostics || [] };
759
780
  }
760
781
 
@@ -774,10 +795,6 @@ function convergenceBlockers(db, baseline, input, rootDir) {
774
795
  if (baseline.repository_branch && snapshot.branch
775
796
  && baseline.repository_branch !== snapshot.branch) blockers.add('BASELINE_BRANCH_STALE');
776
797
  if (!Array.isArray(baseline.scope) || baseline.scope.length === 0) blockers.add('BASELINE_SCOPE_MISSING');
777
- for (const kind of ['discovery', 'product', 'architecture']) {
778
- if (!baseline.spec_refs.some((ref) => ref.kind === kind)) blockers.add(`BASELINE_SPEC_MISSING:${kind}`);
779
- }
780
- for (const blocker of convergenceResearchBlockers(db, baseline)) blockers.add(blocker);
781
798
  for (const blocker of storedSpecBlockers(baseline, rootDir)) blockers.add(blocker);
782
799
  for (const blocker of storedEvidenceBlockers(baseline, rootDir)) blockers.add(blocker);
783
800
  for (const blocker of storedScopeBlockers(baseline, rootDir)) blockers.add(blocker);
@@ -788,29 +805,23 @@ function convergenceBlockers(db, baseline, input, rootDir) {
788
805
  if (baseline.worktree_state === 'dirty' && input.accept_dirty_worktree !== true) {
789
806
  blockers.add('BASELINE_DIRTY_WORKTREE_NOT_ACCEPTED');
790
807
  }
791
- if (baseline.evidence.length === 0) blockers.add('BASELINE_EVIDENCE_MISSING');
792
- if (baseline.mode === 'brownfield' && !baseline.evidence.some((item) => item.kind === 'source')) {
793
- blockers.add('BASELINE_SOURCE_EVIDENCE_MISSING');
794
- }
795
- if (baseline.verification.length === 0) blockers.add('BASELINE_VERIFICATION_MISSING');
796
808
  for (const item of baseline.verification) {
797
- if (item.status === 'not_run') blockers.add(`BASELINE_VERIFICATION_NOT_RUN:${item.name}`);
798
809
  if (item.status === 'known_red') {
799
810
  if (input.accept_known_red !== true) blockers.add(`BASELINE_KNOWN_RED_NOT_ACCEPTED:${item.name}`);
800
811
  if (!item.rationale) blockers.add(`BASELINE_KNOWN_RED_RATIONALE_MISSING:${item.name}`);
801
812
  }
802
813
  }
803
- for (const item of baseline.unknowns.filter((unknown) => unknown.blocking)) {
804
- blockers.add(`BASELINE_UNKNOWN_BLOCKING:${item.summary}`);
805
- }
806
- for (const gap of baseline.gaps.filter((item) => item.status === 'open' && item.blocking)) {
807
- blockers.add(`BASELINE_GAP_BLOCKING:${gap.id}`);
808
- }
809
814
  if (!String(input.approved_by || '').trim()) blockers.add('BASELINE_APPROVER_REQUIRED');
810
- if (String(input.approval_note || '').trim().length < 3) blockers.add('BASELINE_APPROVAL_NOTE_REQUIRED');
815
+ if (!String(input.approval_note || '').trim()) blockers.add('BASELINE_APPROVAL_NOTE_REQUIRED');
811
816
  return [...blockers].sort();
812
817
  }
813
818
 
819
+ function convergenceWarnings(db, baseline) {
820
+ const warnings = new Set(storedReadySemanticWarnings(baseline));
821
+ for (const warning of convergenceResearchBlockers(db, baseline)) warnings.add(warning);
822
+ return [...warnings].sort();
823
+ }
824
+
814
825
  function convergeBaseline(db, input = {}, { rootDir = process.cwd(), emitEvent = true } = {}) {
815
826
  const current = readBaseline(db, input.id);
816
827
  if (!current) throw new BaselineWorkflowError('BASELINE_NOT_FOUND', `baseline ${input.id} not found`);
@@ -818,6 +829,7 @@ function convergeBaseline(db, input = {}, { rootDir = process.cwd(), emitEvent =
818
829
  throw new BaselineWorkflowError('BASELINE_NOT_CONVERGEABLE', `baseline ${current.id} is ${current.status}`);
819
830
  }
820
831
  const blockers = convergenceBlockers(db, current, input, rootDir);
832
+ const warnings = convergenceWarnings(db, current);
821
833
  const ts = nowIso();
822
834
  if (blockers.length > 0) {
823
835
  ops.tx(db, () => {
@@ -829,7 +841,13 @@ function convergeBaseline(db, input = {}, { rootDir = process.cwd(), emitEvent =
829
841
  });
830
842
  }
831
843
  });
832
- return { ready: false, status: 'blocked', blockers, baseline: readBaseline(db, current.id) };
844
+ return {
845
+ ready: false,
846
+ status: 'blocked',
847
+ blockers,
848
+ warnings,
849
+ baseline: readBaseline(db, current.id),
850
+ };
833
851
  }
834
852
  const research = acceptedBaselineResearch(db, current);
835
853
  ops.tx(db, () => {
@@ -841,7 +859,7 @@ function convergeBaseline(db, input = {}, { rootDir = process.cwd(), emitEvent =
841
859
  ).run(
842
860
  String(input.approved_by).trim(), String(input.approval_note).trim(),
843
861
  input.accept_dirty_worktree === true ? 1 : 0,
844
- input.accept_known_red === true ? 1 : 0, research.id, ts, ts, current.id,
862
+ input.accept_known_red === true ? 1 : 0, research?.id || null, ts, ts, current.id,
845
863
  );
846
864
  if (emitEvent) {
847
865
  ops.appendEventInTx(db, {
@@ -849,7 +867,13 @@ function convergeBaseline(db, input = {}, { rootDir = process.cwd(), emitEvent =
849
867
  });
850
868
  }
851
869
  });
852
- return { ready: true, status: 'ready', blockers: [], baseline: readBaseline(db, current.id) };
870
+ return {
871
+ ready: true,
872
+ status: 'ready',
873
+ blockers: [],
874
+ warnings,
875
+ baseline: readBaseline(db, current.id),
876
+ };
853
877
  }
854
878
 
855
879
  function inspectBaseline(db, { rootDir = process.cwd(), id } = {}) {
@@ -880,6 +904,10 @@ function inspectBaseline(db, { rootDir = process.cwd(), id } = {}) {
880
904
  ...(Array.isArray(baseline.evidence) ? storedEvidenceBlockers(baseline, rootDir) : []),
881
905
  ...research.blockers,
882
906
  ];
907
+ const warnings = [
908
+ ...storedReadySemanticWarnings(baseline),
909
+ ...research.warnings,
910
+ ];
883
911
  const snapshot = gitWorktreeSnapshot(
884
912
  rootDir, Array.isArray(baseline.scope) && baseline.scope.length > 0 ? baseline.scope : ['.'],
885
913
  );
@@ -909,7 +937,7 @@ function inspectBaseline(db, { rootDir = process.cwd(), id } = {}) {
909
937
  }
910
938
  return {
911
939
  status: blockers.length === 0 ? 'pass' : 'fail', blockers: [...new Set(blockers)].sort(),
912
- warnings: research.warnings, baseline,
940
+ warnings: [...new Set(warnings)], baseline,
913
941
  };
914
942
  }
915
943
 
@@ -0,0 +1,26 @@
1
+ 'use strict';
2
+
3
+ const crypto = require('node:crypto');
4
+
5
+ function parseJson(value, fallback = {}) {
6
+ if (typeof value !== 'string' || !value.trim()) return fallback;
7
+ try { return JSON.parse(value); } catch { return fallback; }
8
+ }
9
+
10
+ function changeStateDigest(change) {
11
+ if (!change) return null;
12
+ const payload = {
13
+ id: change.id,
14
+ kind: change.kind,
15
+ intent: change.intent,
16
+ docs_impact: change.docs_impact || parseJson(change.docs_impact_json, {}),
17
+ provider_refs: change.provider_refs || parseJson(change.provider_refs_json, {}),
18
+ contract: change.contract || parseJson(change.contract_json, {}),
19
+ classification: change.classification || parseJson(change.classification_json, {}),
20
+ research_disposition: change.research_disposition
21
+ || parseJson(change.research_disposition_json, {}),
22
+ };
23
+ return crypto.createHash('sha256').update(JSON.stringify(payload)).digest('hex');
24
+ }
25
+
26
+ module.exports = { changeStateDigest };
@@ -77,25 +77,8 @@ function requireInside(rootDir, relative, prefix, field) {
77
77
  return resolved;
78
78
  }
79
79
 
80
- function latestWorkflowConsumer(db, changeId) {
81
- const row = db.prepare(
82
- `SELECT id FROM workflow_runs
83
- WHERE change_id = ?
84
- ORDER BY CASE
85
- WHEN status IN ('active', 'blocked', 'ready') THEN 0
86
- WHEN status = 'completed' THEN 1
87
- ELSE 2 END,
88
- CASE kind WHEN 'deliver' THEN 0 WHEN 'plan' THEN 1 WHEN 'research' THEN 2 ELSE 3 END,
89
- updated_at DESC, rowid DESC
90
- LIMIT 1`,
91
- ).get(changeId);
92
- if (!row) {
93
- throw new ChangePacketError(
94
- 'ARTIFACT_CONSUMER_MISSING',
95
- `change ${changeId} has no workflow consumer`,
96
- );
97
- }
98
- return { type: 'workflow', id: row.id, relation: 'consumed_by' };
80
+ function deliveryConsumer() {
81
+ return { type: 'external', id: 'ultra-deliver', relation: 'consumed_by' };
99
82
  }
100
83
 
101
84
  function readCurrentArtifact(db, changeId, kind) {
@@ -118,17 +101,6 @@ function exactBaselineAnchor(db, change, input) {
118
101
  'typed Change delta requires one approved ready baseline',
119
102
  );
120
103
  }
121
- const changeRun = db.prepare(
122
- `SELECT baseline_id FROM workflow_runs
123
- WHERE kind = 'change' AND change_id = ?
124
- ORDER BY started_at DESC, rowid DESC LIMIT 1`,
125
- ).get(change.id);
126
- if (!changeRun || changeRun.baseline_id !== baseline.id) {
127
- throw new ChangePacketError(
128
- 'CHANGE_BASELINE_AUTHORITY_MISMATCH',
129
- `change ${change.id} is not bound to baseline ${baseline.id}`,
130
- );
131
- }
132
104
  const anchor = input.baseline_anchor;
133
105
  if (!anchor || anchor.baseline_id !== baseline.id
134
106
  || (anchor.repository_revision || null) !== (baseline.repository_revision || null)) {
@@ -371,7 +343,7 @@ function recordDelta(db, change, input, { rootDir = process.cwd() } = {}) {
371
343
  let rollbackProgress = null;
372
344
  try {
373
345
  const result = ops.tx(db, () => {
374
- const consumer = latestWorkflowConsumer(db, change.id);
346
+ const consumer = deliveryConsumer();
375
347
  const payloads = delta.mutations.map((mutation) => (
376
348
  artifacts.recordArtifactInTx(db, {
377
349
  id: `change-${change.id}-delta-${mutation.id}`,
@@ -578,7 +550,7 @@ function recordDocumentationReconciliation(
578
550
  let rollbackProgress = null;
579
551
  try {
580
552
  return ops.tx(db, () => {
581
- const fallbackConsumer = latestWorkflowConsumer(db, change.id);
553
+ const fallbackConsumer = deliveryConsumer();
582
554
  const payloads = normalized.reconciliation.documents.map((document, index) => (
583
555
  artifacts.recordArtifactInTx(db, {
584
556
  id: `change-${change.id}-documentation-${index + 1}`,