@fabricorg/sdui-release 0.5.0 → 0.7.0

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.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,72 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.7.0
4
+
5
+ ### Minor Changes
6
+
7
+ - 8436c64: Close what a seventh review found in the previous follow-up, two of them regressions it introduced.
8
+
9
+ **An idempotency conflict is settled, but which way depends on why.** The previous release treated
10
+ every conflict as "never applied" and reported it refused. Only a `parameters` or `actor` conflict
11
+ means that. An `authority_binding` or `action_version` conflict means the _original_ invocation
12
+ exists and may well have applied — the capture-authority adapter mints a fresh binding on every
13
+ attempt, so a first attempt whose response was lost conflicts with itself on the second. Deleting that
14
+ capture and calling it refused was the worst available answer; it is now surfaced as `unknown` under
15
+ status `conflict_prior_invocation`.
16
+
17
+ **A policy block or validation failure is now `unknown`, not `refused`.** Within one attempt both are
18
+ decided before any effect. On a recovery re-execution the worker skips adapter steps that already
19
+ succeeded, then re-evaluates policy with a fresh clock and re-parses parameters against a possibly
20
+ newer action, and either can flip after effects committed. Unlike the authority path, the host
21
+ persists no execution reason for these, so the client cannot tell first delivery from recovery; it
22
+ errs toward a person checking. The precise fix is for the host to persist `executionReason` on failed
23
+ invocations, which is a store change and is recorded rather than attempted here.
24
+
25
+ **`failed` means "never delivered" again.** A store fault after a successful delivery was being counted
26
+ in it; it now lives only in `storeFailures`. The 0.3.0 changelog's disclosure misdescribed what 0.2.0
27
+ had changed — `parked` never shipped as a count; only `expired` did — and it is corrected in place.
28
+
29
+ Every validation pass now sees a duplicated contract once, so one root cause produces one finding
30
+ rather than one per copy. `deriveAuthorizationGrants` no longer emits a reference for an intent whose
31
+ action the contract does not declare, matching what promotion resolves. A superseded comment above
32
+ `PORT_ID` that asserted the claim the fix retracted is removed.
33
+
34
+ ## 0.6.0
35
+
36
+ ### Minor Changes
37
+
38
+ - 4780974: Close what a sixth review found in the previously unreviewed fix batch, and disclose a breaking change
39
+ that batch shipped under a minor.
40
+
41
+ **Disclosure, first.** `experience-runtime@0.2.0` changed `OfflineReplaySummary.expired` from a
42
+ count to an array of records — breaking for a 0.1.0 consumer, for whom `if (summary.expired)` becomes
43
+ permanently true. (`parked` was introduced and reshaped inside the 0.2.0 window and never shipped as
44
+ a count, so no published consumer could have depended on it.) The 0.2.0 changelog did disclose the
45
+ change, but one of its four aggregated entries still described counts. This release adds
46
+ `storeFailures`, a **required** field on the returned summary: source-breaking for anything that
47
+ constructs an `OfflineReplaySummary` or implements the exported `OfflineCaptureQueue`, which a test
48
+ double may. Treat the shape as changed.
49
+
50
+ **Offline replay.** An authority refusal on a _recovery_ re-execution lands after adapter steps
51
+ already committed, since the lease worker re-runs authorization but skips succeeded steps rather
52
+ than undoing them; it is now reported as `unknown`, not `refused`, using the `executionReason` the
53
+ platform persists. A store that throws mid-replay now degrades one record instead of rejecting the
54
+ batch and losing every entry already removed. An idempotency conflict is settled — the key was reused
55
+ for a different command and retrying yields the same conflict forever — so it reaches a person rather
56
+ than looping. An action-version mismatch is transient, since it fires mid-rollout. An adapter
57
+ returning nothing keeps the capture queued rather than draining the queue into `unknown`.
58
+
59
+ **Release validation.** A grant that no supplied capability publishes now fails validation instead of
60
+ dying at promotion with "route missing"; ambiguity was scoped to granted references and had covered
61
+ only the two-or-more case. A contract supplied twice yields exactly one finding — the previous fix
62
+ still reported each of its granted views as declared twice, and the changelog claimed otherwise. An
63
+ intent whose action the contract does not declare no longer counts toward ambiguity, matching what
64
+ promotion resolves.
65
+
66
+ **Ports.** `PORT_ID` accepts any segment shape a manifest permits after the first dot, so every
67
+ namespaced name a capability can declare is a name a port can define. Dotless names remain
68
+ undefinable by design.
69
+
3
70
  ## 0.5.0
4
71
 
5
72
  ### Minor Changes
package/dist/index.cjs CHANGED
@@ -454,7 +454,9 @@ function buildKnownActionIntents(contracts, add, grantedIntents) {
454
454
  const occurrences = /* @__PURE__ */ new Map();
455
455
  for (const contract of contracts) {
456
456
  const experience = contract.extensions?.["fabric.experience/v1"];
457
+ const declaredActions = new Set(contract.actions.map((action) => action.actionId));
457
458
  for (const intent of experience?.actionIntents ?? []) {
459
+ if (!declaredActions.has(intent.actionId)) continue;
458
460
  const shortName2 = intent.name.startsWith(`${contract.namespace}.`) ? intent.name.slice(contract.namespace.length + 1) : intent.name;
459
461
  const reference = `${contract.namespace}/${shortName2}`;
460
462
  if (grantedIntents === void 0 || grantedIntents.has(`capability://${reference}`)) {
@@ -713,6 +715,25 @@ function validateSduiRelease(input) {
713
715
  ...resolved.remote ? { remote: resolved.remote } : {}
714
716
  });
715
717
  }
718
+ const contractNamespaces = /* @__PURE__ */ new Set();
719
+ const distinctContracts = contracts.filter((contract) => {
720
+ if (contractNamespaces.has(contract.namespace)) {
721
+ add(
722
+ "ambiguous_capability_reference",
723
+ `contracts.${contract.namespace}`,
724
+ `contract for "${contract.namespace}" is supplied more than once`
725
+ );
726
+ return false;
727
+ }
728
+ contractNamespaces.add(contract.namespace);
729
+ return true;
730
+ });
731
+ const seenDocumentNamespaces = /* @__PURE__ */ new Set();
732
+ const distinctDocuments = input.contracts.filter((document) => {
733
+ if (seenDocumentNamespaces.has(document.capability.namespace)) return false;
734
+ seenDocumentNamespaces.add(document.capability.namespace);
735
+ return true;
736
+ });
716
737
  {
717
738
  for (const pack of resolvedPacks) {
718
739
  const approved = input.assembly.componentPacks.find(
@@ -764,7 +785,7 @@ function validateSduiRelease(input) {
764
785
  );
765
786
  }
766
787
  }
767
- for (const document of input.contracts) {
788
+ for (const document of distinctDocuments) {
768
789
  const approved = input.assembly.capabilities.find(
769
790
  (capability) => capability.namespace === document.capability.namespace && capability.usageContractDigest === (0, import_assembly2.computeUsageContractDocumentDigest)(document)
770
791
  );
@@ -784,23 +805,32 @@ function validateSduiRelease(input) {
784
805
  }
785
806
  }
786
807
  const coreComponents = new Set(input.coreComponents ?? []);
787
- const knownViews = buildKnownViews(contracts);
808
+ const knownViews = buildKnownViews(distinctContracts);
788
809
  const grantedViews = buildGrantedReferences(input.grants.views, "grants.views");
789
810
  const grantedIntents = buildGrantedReferences(input.grants.intents, "grants.intents");
790
- const knownIntents = buildKnownActionIntents(contracts, add, grantedIntents);
791
- reportAmbiguousGrantedViews(contracts, grantedViews, add);
792
- const contractNamespaces = /* @__PURE__ */ new Set();
793
- for (const contract of contracts) {
794
- if (contractNamespaces.has(contract.namespace)) {
811
+ const knownIntents = buildKnownActionIntents(distinctContracts, add, grantedIntents);
812
+ reportAmbiguousGrantedViews(distinctContracts, grantedViews, add);
813
+ for (const reference of grantedViews) {
814
+ const parsed = parseCapabilityRef(reference);
815
+ if (parsed && !knownViews.has(`${parsed.namespace}/${parsed.name}`)) {
795
816
  add(
796
- "ambiguous_capability_reference",
797
- `contracts.${contract.namespace}`,
798
- `contract for "${contract.namespace}" is supplied more than once`
817
+ "unauthorized_data_ref",
818
+ `grants.views.${reference}`,
819
+ `release grants "${reference}", which no supplied capability publishes; promotion would fail to route it`
820
+ );
821
+ }
822
+ }
823
+ for (const reference of grantedIntents) {
824
+ const parsed = parseCapabilityRef(reference);
825
+ if (parsed && !knownIntents.has(`${parsed.namespace}/${parsed.name}`)) {
826
+ add(
827
+ "mutation_bypass",
828
+ `grants.intents.${reference}`,
829
+ `release grants "${reference}", which no supplied capability declares as an intent backed by a declared action; promotion would fail to route it`
799
830
  );
800
831
  }
801
- contractNamespaces.add(contract.namespace);
802
832
  }
803
- validateIntentActionDeclarations(contracts, add);
833
+ validateIntentActionDeclarations(distinctContracts, add);
804
834
  const fragmentGrants = /* @__PURE__ */ new Map();
805
835
  for (const [fragmentId, declared] of Object.entries(input.grants.fragments ?? {})) {
806
836
  for (const view of declared.views ?? []) {
@@ -1102,7 +1132,9 @@ function deriveAuthorizationGrants(contracts, held, options = {}) {
1102
1132
  const reference = `capability://${contract.namespace}/${shortName(view.name, contract.namespace, "/")}`;
1103
1133
  if (!bound || bound.views.has(reference)) views.push(reference);
1104
1134
  }
1135
+ const declaredActions = new Set(contract.actions.map((action) => action.actionId));
1105
1136
  for (const intent of actionIntentsOf(contract)) {
1137
+ if (!declaredActions.has(intent.actionId)) continue;
1106
1138
  const reference = `capability://${contract.namespace}/${shortName(intent.name, contract.namespace, ".")}`;
1107
1139
  if (!bound || bound.intents.has(reference)) intents.push(reference);
1108
1140
  }