@mindexed/cfact 1.1.0 → 1.1.1

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.
@@ -648,7 +648,31 @@ function resolveSource(opts, pin) {
648
648
  }
649
649
  const base = channelBase(opts, pin);
650
650
  if (base) {
651
- return { kind: 'url', via: 'studio', ref: `${base.replace(/\/+$/, '')}/${asset}`, bearerEnv: ENGINE_TOKEN_NAMES };
651
+ // THIS CHANNEL NOW CARRIES A FALLBACK TOO, AND IT USED TO BE ARGUED IT DID
652
+ // NOT NEED ONE (FF-278, ADR-0203). ADR-0194 reasoned that the studio is
653
+ // "STRUCTURAL — version-keyed end to end", so both prefixes reach the same
654
+ // stored object and no fallback is warranted. That reasoning was correct
655
+ // about the STORE and wrong about the SERVING PATH: a filename validator sits
656
+ // in FRONT of the version-keyed lookup, and until it was widened and
657
+ // DEPLOYED it rejected `cfact-engine-*` outright. Measured on a real
658
+ // consumer: every version 400ed under the new name while the legacy name
659
+ // returned a valid tarball, so the DEFAULT channel was dead for every
660
+ // consumer on installer 1.1.0+ with no override.
661
+ //
662
+ // The general form is the part worth keeping: a structural claim about
663
+ // another repo must name WHICH CODE PATH it is about. "The store is keyed on
664
+ // version" and "the endpoint accepts this name" are different statements
665
+ // about different code, and nothing in this repo can check either one
666
+ // (rules/narrative-drift.md class 1). Where the claim is unverifiable from
667
+ // here, the addressed site owes a fallback — which costs one extra request
668
+ // ONLY on a failure that is already fatal, and nothing at all on success.
669
+ return {
670
+ kind: 'url',
671
+ via: 'studio',
672
+ ref: `${base.replace(/\/+$/, '')}/${asset}`,
673
+ fallbackRef: `${base.replace(/\/+$/, '')}/${LEGACY_ASSET(version)}`,
674
+ bearerEnv: ENGINE_TOKEN_NAMES,
675
+ };
652
676
  }
653
677
 
654
678
  // OPT-IN FALLBACK channel (ADR-0093, narrowed by ADR-0168): a GitHub Release
@@ -662,7 +686,8 @@ function resolveSource(opts, pin) {
662
686
  // private repo, prefer the studio channel above, or --url with the exact asset.
663
687
  const repo = opts.repo || pin.repo || DEFAULT_REPO;
664
688
  const releaseBase = `https://github.com/${repo}/releases/download/engine-v${version}`;
665
- // THIS CHANNEL NEEDS A FALLBACK AND THE STUDIO DOES NOT (ADR-0194). The
689
+ // WHY THIS CHANNEL NEEDS ITS FALLBACK (ADR-0194; the studio has one too as of
690
+ // ADR-0203 — see below, and do NOT "restore the asymmetry"). The
666
691
  // publisher attaches both asset names, but only to releases it publishes FROM
667
692
  // NOW ON — and a published release is immutable (ADR-0128: an existing tag is
668
693
  // an idempotent skip, never an asset re-upload). So on a NAME-keyed channel the
@@ -672,14 +697,19 @@ function resolveSource(opts, pin) {
672
697
  //
673
698
  // That is ADR-0192's read-vs-address proviso failing in the one direction the
674
699
  // ADR did not take it. Its test asks whether something outside this repo keeps
675
- // the old value resolving; the answer is structural for the studio (version-
676
- // keyed, so both names always reach the same object) and merely FORWARD-LOOKING
677
- // here. Where the proviso holds in only one direction, the addressed site owes a
678
- // fallback for the other.
700
+ // the old value resolving, and here the answer is merely FORWARD-LOOKING: every
701
+ // already-published release carries the legacy asset alone. Where the proviso
702
+ // holds in only one direction, the addressed site owes a fallback for the other.
679
703
  //
680
- // Scoped to this branch on purpose: the studio needs no fallback, and giving it
681
- // one would spend a wasted round-trip on the DEFAULT channel to paper over a
682
- // problem it does not have.
704
+ // BOTH CHANNELS NOW CARRY ONE, and this comment used to say the opposite —
705
+ // "the studio needs no fallback, and giving it one would spend a wasted
706
+ // round-trip on the DEFAULT channel to paper over a problem it does not have."
707
+ // The studio DID have that problem (FF-278, ADR-0203): its serving-path
708
+ // validator rejected the new name for every version, and the round-trip is not
709
+ // wasted because a fallback costs a request only on a failure that is otherwise
710
+ // fatal. The two channels reach the same need by different routes — this one
711
+ // because published releases are immutable, the studio because a claim about
712
+ // another repo's code path could not be checked from here.
683
713
  return {
684
714
  kind: 'url',
685
715
  via: 'github-releases',
@@ -996,12 +1026,23 @@ async function fetchToFile(source, destFile) {
996
1026
  } catch (e) {
997
1027
  die(`download failed — ${e.message}`);
998
1028
  }
999
- // A 404 with a fallback is the pre-ADR-0194 release: retry ONCE on the legacy
1000
- // asset name. Deliberately narrow on all three axes — only 404 (a 401 means the
1001
- // token, and retrying would report the wrong cause), only when resolveSource set
1002
- // a fallbackRef (the github channel and no other), and only once (two names
1003
- // exist, so a loop could only re-request one of them).
1004
- if (res.status === 404 && source.fallbackRef) {
1029
+ // A 404 or a 400 with a fallback means THE NAME DID NOT RESOLVE: retry ONCE on
1030
+ // the legacy asset name. Still deliberately narrow — only when resolveSource
1031
+ // set a fallbackRef, and only once (two names exist, so a loop could only
1032
+ // re-request one of them).
1033
+ //
1034
+ // 400 joined 404 with ADR-0203. They are one condition stated by two servers:
1035
+ // a name-keyed store answers "no such asset" with 404, and a server that
1036
+ // VALIDATES the filename before looking anything up rejects it with 400 —
1037
+ // measured, verbatim `{"error":"Invalid engine tarball filename."}` from the
1038
+ // studio for every version under the new prefix. A 404-only trigger reads that
1039
+ // as fatal and never tries the name that works.
1040
+ //
1041
+ // 401 STAYS EXCLUDED, and that exclusion is the load-bearing half: a 401 means
1042
+ // the token, so retrying under a second name would burn a request and then
1043
+ // report the wrong cause — and the stripped-bearer diagnosis below exists
1044
+ // precisely because a 401 here is already hard to attribute.
1045
+ if ((res.status === 404 || res.status === 400) && source.fallbackRef) {
1005
1046
  info(` not found under the current asset name; retrying ${source.fallbackRef}`);
1006
1047
  try {
1007
1048
  res = await fetch(source.fallbackRef, { headers, redirect: 'follow' });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mindexed/cfact",
3
- "version": "1.1.0",
3
+ "version": "1.1.1",
4
4
  "description": "Clean Orthogonal Architecture Software Factory Framework",
5
5
  "bin": {
6
6
  "claude-factory": "bin/claude-factory.js",
@@ -66,10 +66,13 @@
66
66
  "publish:release": "node maintainer-hooks/publish-release.js",
67
67
  "gate:consistency": "npm run gate:matrix-sync && npm run gate:compliance && npm run gate:doc-links && npm run gate:command-metadata && npm run gate:tracked-symlinks && npm run gate:agents-md-sync && npm run gate:ripple-coverage && npm run gate:profile-docs && npm run gate:registry && npm run gate:adr-template-edges && npm run gate:installer-publish-drift",
68
68
  "gate:boundaries": "bash hooks/check-matrix.sh domain && bash hooks/check-matrix.sh components && bash hooks/check-matrix.sh controllers && bash hooks/check-matrix.sh secrets",
69
- "ci": "npm run gate:consistency && npm run gate:boundaries && npm run gate:profile-coverage && npm run gate:changelog && npm run test:dc-generator && npm run test:substance-generator && npm run test:json-flush && npm run test:installer-exit && npm run test:installer-channel && npm run test:installer-wiring && npm run test:tar-invocation && npm run test:framework-agents-mcp && npm run test:controller-gates && npm run test:git-tree-sweep && npm run test:secrets-two-stage && npm run test:profile-paths && npm run test:registry-gate && npm run test:adr-template-edges && npm run test:adr-path-constants && npm run test:graph-strip && npm run test:graph-registry && npm run test:frontmatter-readers && npm run test:tarball-reproducible && npm run test:release-version && npm run test:consumer-checklist && npm run test:workflow-spine && npm run test:command-shape && npm run test:change-record && npm run test:installer-publish-drift && npm run test:grants-authored && npm run test:test-capability && npm run test:installer-lifecycle && npm run test:gate-three-state && npm run test:refresh-compliance && npm run test:gate-coverage && npm run test:installer-shadowing && npm run test:feature-record",
69
+ "ci": "npm run gate:consistency && npm run gate:boundaries && npm run gate:profile-coverage && npm run gate:changelog && npm run test:dc-generator && npm run test:substance-generator && npm run test:json-flush && npm run test:installer-exit && npm run test:installer-channel && npm run test:installer-wiring && npm run test:tar-invocation && npm run test:framework-agents-mcp && npm run test:controller-gates && npm run test:git-tree-sweep && npm run test:secrets-two-stage && npm run test:profile-paths && npm run test:registry-gate && npm run test:adr-template-edges && npm run test:adr-path-constants && npm run test:graph-strip && npm run test:graph-registry && npm run test:frontmatter-readers && npm run test:tarball-reproducible && npm run test:release-version && npm run test:consumer-checklist && npm run test:workflow-spine && npm run test:command-shape && npm run test:change-record && npm run test:installer-publish-drift && npm run test:grants-authored && npm run test:test-capability && npm run test:installer-lifecycle && npm run test:gate-three-state && npm run test:refresh-compliance && npm run test:gate-coverage && npm run test:installer-shadowing && npm run test:feature-record && npm run test:consumer-docs-migration && npm run test:npm-deprecation",
70
70
  "test:gate-three-state": "node scripts/test-gate-three-state.js",
71
71
  "test:refresh-compliance": "node scripts/test-refresh-compliance.js",
72
- "test:gate-coverage": "node scripts/test-gate-coverage.js"
72
+ "test:gate-coverage": "node scripts/test-gate-coverage.js",
73
+ "test:consumer-docs-migration": "node scripts/test-consumer-docs-migration.js",
74
+ "test:sot-templates-pointer": "node scripts/test-sot-templates-pointer.js",
75
+ "test:npm-deprecation": "node scripts/test-npm-deprecation.js"
73
76
  },
74
77
  "keywords": [],
75
78
  "author": "",