@metaobjectsdev/migrate-ts 1.0.2 → 1.0.4-rc.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.
@@ -38,6 +38,18 @@ export interface ComputeDriftOptions {
38
38
  * failing on tables migrate does not own is incoherent.
39
39
  */
40
40
  inScope?: ObjectScopePredicate;
41
+ /**
42
+ * FR-023 — objects a DEPENDENCY owns. Such an object is loaded so this project's own
43
+ * model can resolve against it and is governed by nobody here unless `inScope`
44
+ * admits it: it leaves the expected side, is suppressed on the actual side, and —
45
+ * unlike an `inScope` exclusion — takes the publisher's database schema out of the
46
+ * run's scope with it, so a table the publisher never exported is not reported as an
47
+ * extra table in a schema this project does not manage (migrate-ts `scope.ts`).
48
+ *
49
+ * `verify --db` and `migrate` share this declaration for the same reason they share
50
+ * `inScope`. Omit for a project with no dependencies (unchanged behavior).
51
+ */
52
+ imported?: ObjectScopePredicate;
41
53
  }
42
54
  export interface DriftResult extends DiffResult {
43
55
  /**
@@ -56,6 +68,13 @@ export interface DriftResult extends DiffResult {
56
68
  * `GovernedScope`, which is what `excludeFromSnapshot` takes.
57
69
  */
58
70
  declaredSchemas: readonly string[] | undefined;
71
+ /**
72
+ * The subset of `outOfScope` a DEPENDENCY contributed (FR-023). Reported for the
73
+ * same reason `declaredSchemas` is: so the caller states WHY an object was left out
74
+ * without re-deriving the decision from a second expected-schema build, which could
75
+ * come to disagree with the one this comparison actually made.
76
+ */
77
+ importedOutOfScope: readonly string[] | undefined;
59
78
  }
60
79
  /**
61
80
  * Compute the drift between an ALREADY-INTROSPECTED schema snapshot and the
@@ -1 +1 @@
1
- {"version":3,"file":"drift.d.ts","sourceRoot":"","sources":["../../src/drift/drift.ts"],"names":[],"mappings":"AAeA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AACrC,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,0BAA0B,CAAC;AACzD,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,0BAA0B,CAAC;AAKrE,OAAO,EAAyC,KAAK,oBAAoB,EAAE,MAAM,aAAa,CAAC;AAC/F,OAAO,KAAK,EAAE,YAAY,EAAE,OAAO,EAAE,UAAU,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAErF,MAAM,WAAW,mBAAmB;IAClC;;;;OAIG;IACH,KAAK,CAAC,EAAE,YAAY,CAAC;IACrB;;;;OAIG;IACH,oBAAoB,CAAC,EAAE,oBAAoB,CAAC;IAC5C;;;OAGG;IACH,YAAY,CAAC,EAAE,MAAM,EAAE,CAAC;IACxB;;;;OAIG;IACH,KAAK,CAAC,EAAE,SAAS,OAAO,uBAAuB,EAAE,iBAAiB,EAAE,CAAC;IACrE;;;;;;;;;OASG;IACH,OAAO,CAAC,EAAE,oBAAoB,CAAC;CAChC;AAED,MAAM,WAAW,WAAY,SAAQ,UAAU;IAC7C;;;;OAIG;IACH,UAAU,EAAE,SAAS,MAAM,EAAE,CAAC;IAC9B;;;;;;;;OAQG;IACH,eAAe,EAAE,SAAS,MAAM,EAAE,GAAG,SAAS,CAAC;CAChD;AAED;;;;;;;;;;;GAWG;AACH,wBAAsB,sBAAsB,CAC1C,MAAM,EAAE,cAAc,EACtB,OAAO,EAAE,OAAO,EAChB,QAAQ,EAAE,QAAQ,EAClB,IAAI,CAAC,EAAE,mBAAmB,GACzB,OAAO,CAAC,WAAW,CAAC,CA2BtB;AAED;;;;;;GAMG;AACH,wBAAsB,YAAY,CAChC,EAAE,EAAE,MAAM,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,EACnC,OAAO,EAAE,OAAO,EAChB,QAAQ,EAAE,QAAQ,EAClB,IAAI,CAAC,EAAE,mBAAmB,GACzB,OAAO,CAAC,WAAW,CAAC,CAGtB"}
1
+ {"version":3,"file":"drift.d.ts","sourceRoot":"","sources":["../../src/drift/drift.ts"],"names":[],"mappings":"AAeA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AACrC,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,0BAA0B,CAAC;AACzD,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,0BAA0B,CAAC;AAKrE,OAAO,EAAyC,KAAK,oBAAoB,EAAE,MAAM,aAAa,CAAC;AAC/F,OAAO,KAAK,EAAE,YAAY,EAAE,OAAO,EAAE,UAAU,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAErF,MAAM,WAAW,mBAAmB;IAClC;;;;OAIG;IACH,KAAK,CAAC,EAAE,YAAY,CAAC;IACrB;;;;OAIG;IACH,oBAAoB,CAAC,EAAE,oBAAoB,CAAC;IAC5C;;;OAGG;IACH,YAAY,CAAC,EAAE,MAAM,EAAE,CAAC;IACxB;;;;OAIG;IACH,KAAK,CAAC,EAAE,SAAS,OAAO,uBAAuB,EAAE,iBAAiB,EAAE,CAAC;IACrE;;;;;;;;;OASG;IACH,OAAO,CAAC,EAAE,oBAAoB,CAAC;IAC/B;;;;;;;;;;OAUG;IACH,QAAQ,CAAC,EAAE,oBAAoB,CAAC;CACjC;AAED,MAAM,WAAW,WAAY,SAAQ,UAAU;IAC7C;;;;OAIG;IACH,UAAU,EAAE,SAAS,MAAM,EAAE,CAAC;IAC9B;;;;;;;;OAQG;IACH,eAAe,EAAE,SAAS,MAAM,EAAE,GAAG,SAAS,CAAC;IAC/C;;;;;OAKG;IACH,kBAAkB,EAAE,SAAS,MAAM,EAAE,GAAG,SAAS,CAAC;CACnD;AAED;;;;;;;;;;;GAWG;AACH,wBAAsB,sBAAsB,CAC1C,MAAM,EAAE,cAAc,EACtB,OAAO,EAAE,OAAO,EAChB,QAAQ,EAAE,QAAQ,EAClB,IAAI,CAAC,EAAE,mBAAmB,GACzB,OAAO,CAAC,WAAW,CAAC,CA6BtB;AAED;;;;;;GAMG;AACH,wBAAsB,YAAY,CAChC,EAAE,EAAE,MAAM,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,EACnC,OAAO,EAAE,OAAO,EAChB,QAAQ,EAAE,QAAQ,EAClB,IAAI,CAAC,EAAE,mBAAmB,GACzB,OAAO,CAAC,WAAW,CAAC,CAGtB"}
@@ -36,7 +36,7 @@ export async function computeDriftFromActual(actual, dialect, metadata, opts) {
36
36
  ? { columnNamingStrategy: opts.columnNamingStrategy }
37
37
  : {}),
38
38
  ...(opts?.views !== undefined ? { views: opts.views } : {}),
39
- }), opts?.inScope);
39
+ }), opts?.inScope, opts?.imported !== undefined ? { imported: opts.imported } : undefined);
40
40
  const result = await diff({
41
41
  // The three scoped-diff obligations as one value (see scope.ts's header):
42
42
  // the narrowed expected side, `unmanagedNames` merging @unmanaged with the
@@ -52,6 +52,7 @@ export async function computeDriftFromActual(actual, dialect, metadata, opts) {
52
52
  ...result,
53
53
  outOfScope: scoped.outOfScope,
54
54
  declaredSchemas: scoped.declaredSchemas,
55
+ importedOutOfScope: scoped.importedOutOfScope,
55
56
  };
56
57
  }
57
58
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"drift.js","sourceRoot":"","sources":["../../src/drift/drift.ts"],"names":[],"mappings":"AAAA,4EAA4E;AAC5E,gEAAgE;AAChE,wEAAwE;AACxE,EAAE;AACF,+EAA+E;AAC/E,6EAA6E;AAC7E,6EAA6E;AAC7E,4EAA4E;AAC5E,8CAA8C;AAC9C,EAAE;AACF,gFAAgF;AAChF,+EAA+E;AAC/E,iFAAiF;AACjF,oFAAoF;AAKpF,OAAO,EAAE,iCAAiC,EAAE,MAAM,uBAAuB,CAAC;AAC1E,OAAO,EAAE,UAAU,EAAE,MAAM,wBAAwB,CAAC;AACpD,OAAO,EAAE,IAAI,EAAE,MAAM,kBAAkB,CAAC;AACxC,OAAO,EAAE,qBAAqB,EAAE,MAAM,iBAAiB,CAAC;AACxD,OAAO,EAAE,mBAAmB,EAAE,gBAAgB,EAA6B,MAAM,aAAa,CAAC;AA2D/F;;;;;;;;;;;GAWG;AACH,MAAM,CAAC,KAAK,UAAU,sBAAsB,CAC1C,MAAsB,EACtB,OAAgB,EAChB,QAAkB,EAClB,IAA0B;IAE1B,MAAM,MAAM,GAAG,mBAAmB,CAChC,iCAAiC,CAAC,QAAQ,EAAE;QAC1C,OAAO;QACP,GAAG,CAAC,IAAI,EAAE,oBAAoB,KAAK,SAAS;YAC1C,CAAC,CAAC,EAAE,oBAAoB,EAAE,IAAI,CAAC,oBAAoB,EAAE;YACrD,CAAC,CAAC,EAAE,CAAC;QACP,GAAG,CAAC,IAAI,EAAE,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,IAAI,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;KAC5D,CAAC,EACF,IAAI,EAAE,OAAO,CACd,CAAC;IACF,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC;QACxB,0EAA0E;QAC1E,2EAA2E;QAC3E,2EAA2E;QAC3E,0EAA0E;QAC1E,GAAG,gBAAgB,CAAC,MAAM,EAAE,qBAAqB,CAAC,QAAQ,CAAC,CAAC;QAC5D,MAAM;QACN,OAAO;QACP,KAAK,EAAE,IAAI,EAAE,KAAK,IAAI,EAAE;QACxB,GAAG,CAAC,IAAI,EAAE,YAAY,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,YAAY,EAAE,IAAI,CAAC,YAAY,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;KACjF,CAAC,CAAC;IACH,OAAO;QACL,GAAG,MAAM;QACT,UAAU,EAAE,MAAM,CAAC,UAAU;QAC7B,eAAe,EAAE,MAAM,CAAC,eAAe;KACxC,CAAC;AACJ,CAAC;AAED;;;;;;GAMG;AACH,MAAM,CAAC,KAAK,UAAU,YAAY,CAChC,EAAmC,EACnC,OAAgB,EAChB,QAAkB,EAClB,IAA0B;IAE1B,MAAM,MAAM,GAAG,MAAM,UAAU,CAAC,EAAE,EAAE,OAAO,CAAC,CAAC;IAC7C,OAAO,sBAAsB,CAAC,MAAM,EAAE,OAAO,EAAE,QAAQ,EAAE,IAAI,CAAC,CAAC;AACjE,CAAC"}
1
+ {"version":3,"file":"drift.js","sourceRoot":"","sources":["../../src/drift/drift.ts"],"names":[],"mappings":"AAAA,4EAA4E;AAC5E,gEAAgE;AAChE,wEAAwE;AACxE,EAAE;AACF,+EAA+E;AAC/E,6EAA6E;AAC7E,6EAA6E;AAC7E,4EAA4E;AAC5E,8CAA8C;AAC9C,EAAE;AACF,gFAAgF;AAChF,+EAA+E;AAC/E,iFAAiF;AACjF,oFAAoF;AAKpF,OAAO,EAAE,iCAAiC,EAAE,MAAM,uBAAuB,CAAC;AAC1E,OAAO,EAAE,UAAU,EAAE,MAAM,wBAAwB,CAAC;AACpD,OAAO,EAAE,IAAI,EAAE,MAAM,kBAAkB,CAAC;AACxC,OAAO,EAAE,qBAAqB,EAAE,MAAM,iBAAiB,CAAC;AACxD,OAAO,EAAE,mBAAmB,EAAE,gBAAgB,EAA6B,MAAM,aAAa,CAAC;AA8E/F;;;;;;;;;;;GAWG;AACH,MAAM,CAAC,KAAK,UAAU,sBAAsB,CAC1C,MAAsB,EACtB,OAAgB,EAChB,QAAkB,EAClB,IAA0B;IAE1B,MAAM,MAAM,GAAG,mBAAmB,CAChC,iCAAiC,CAAC,QAAQ,EAAE;QAC1C,OAAO;QACP,GAAG,CAAC,IAAI,EAAE,oBAAoB,KAAK,SAAS;YAC1C,CAAC,CAAC,EAAE,oBAAoB,EAAE,IAAI,CAAC,oBAAoB,EAAE;YACrD,CAAC,CAAC,EAAE,CAAC;QACP,GAAG,CAAC,IAAI,EAAE,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,IAAI,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;KAC5D,CAAC,EACF,IAAI,EAAE,OAAO,EACb,IAAI,EAAE,QAAQ,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,QAAQ,EAAE,IAAI,CAAC,QAAQ,EAAE,CAAC,CAAC,CAAC,SAAS,CACvE,CAAC;IACF,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC;QACxB,0EAA0E;QAC1E,2EAA2E;QAC3E,2EAA2E;QAC3E,0EAA0E;QAC1E,GAAG,gBAAgB,CAAC,MAAM,EAAE,qBAAqB,CAAC,QAAQ,CAAC,CAAC;QAC5D,MAAM;QACN,OAAO;QACP,KAAK,EAAE,IAAI,EAAE,KAAK,IAAI,EAAE;QACxB,GAAG,CAAC,IAAI,EAAE,YAAY,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,YAAY,EAAE,IAAI,CAAC,YAAY,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;KACjF,CAAC,CAAC;IACH,OAAO;QACL,GAAG,MAAM;QACT,UAAU,EAAE,MAAM,CAAC,UAAU;QAC7B,eAAe,EAAE,MAAM,CAAC,eAAe;QACvC,kBAAkB,EAAE,MAAM,CAAC,kBAAkB;KAC9C,CAAC;AACJ,CAAC;AAED;;;;;;GAMG;AACH,MAAM,CAAC,KAAK,UAAU,YAAY,CAChC,EAAmC,EACnC,OAAgB,EAChB,QAAkB,EAClB,IAA0B;IAE1B,MAAM,MAAM,GAAG,MAAM,UAAU,CAAC,EAAE,EAAE,OAAO,CAAC,CAAC;IAC7C,OAAO,sBAAsB,CAAC,MAAM,EAAE,OAAO,EAAE,QAAQ,EAAE,IAAI,CAAC,CAAC;AACjE,CAAC"}
@@ -35,7 +35,12 @@ export declare function isRequired(field: MetaData): boolean;
35
35
  * omits actions when the DB value is "no-action", so the expected side does the same
36
36
  * to keep round-trip diffs clean.
37
37
  *
38
- * If multiple relationships target the same entity (rare), the first one is used.
38
+ * When more than one relationship targets the same entity (Match.homeTeam /
39
+ * awayTeam both -> Team), each is correlated to its OWN identity.reference via
40
+ * the shared relationship<->reference ladder (#368 round 2) — never the first
41
+ * match. When the correlation is genuinely ambiguous, it contributes nothing
42
+ * rather than an arbitrary — possibly wrong — action (see the tier-2/tier-3
43
+ * bodies below for the exact rules).
39
44
  *
40
45
  * The single `as FkAction` cast in normalize() is safe because REFERENTIAL_ACTIONS
41
46
  * (metadata package) and FkAction (migrate-ts/src/types.ts) are the same four-value
@@ -1 +1 @@
1
- {"version":3,"file":"referential-actions.d.ts","sourceRoot":"","sources":["../src/referential-actions.ts"],"names":[],"mappings":"AAAA,OAAO,EASL,KAAK,UAAU,EAEf,KAAK,qBAAqB,EAC1B,KAAK,QAAQ,EACd,MAAM,0BAA0B,CAAC;AAClC,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAO3C,wBAAgB,kBAAkB,CAAC,QAAQ,EAAE,QAAQ,GAAG,MAAM,EAAE,CAO/D;AAED,wBAAgB,SAAS,CAAC,MAAM,EAAE,UAAU,EAAE,IAAI,EAAE,MAAM,GAAG,QAAQ,GAAG,SAAS,CAKhF;AAED,wBAAgB,UAAU,CAAC,KAAK,EAAE,QAAQ,GAAG,OAAO,CAOnD;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAwCG;AACH,wBAAgB,yBAAyB,CACvC,MAAM,EAAE,UAAU,EAClB,GAAG,EAAE,qBAAqB,GACzB;IAAE,QAAQ,EAAE,QAAQ,GAAG,SAAS,CAAC;IAAC,QAAQ,EAAE,QAAQ,GAAG,SAAS,CAAA;CAAE,CAiFpE;AAkED;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,0BAA0B,CACxC,MAAM,EAAE,UAAU,EAClB,GAAG,EAAE,qBAAqB,EAC1B,QAAQ,EAAE,QAAQ,GAAG,SAAS,EAC9B,cAAc,EAAE,MAAM,GACrB,IAAI,CAeN"}
1
+ {"version":3,"file":"referential-actions.d.ts","sourceRoot":"","sources":["../src/referential-actions.ts"],"names":[],"mappings":"AAAA,OAAO,EAUL,KAAK,UAAU,EAEf,KAAK,qBAAqB,EAC1B,KAAK,QAAQ,EACd,MAAM,0BAA0B,CAAC;AAClC,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAO3C,wBAAgB,kBAAkB,CAAC,QAAQ,EAAE,QAAQ,GAAG,MAAM,EAAE,CAO/D;AAED,wBAAgB,SAAS,CAAC,MAAM,EAAE,UAAU,EAAE,IAAI,EAAE,MAAM,GAAG,QAAQ,GAAG,SAAS,CAKhF;AAED,wBAAgB,UAAU,CAAC,KAAK,EAAE,QAAQ,GAAG,OAAO,CAOnD;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6CG;AACH,wBAAgB,yBAAyB,CACvC,MAAM,EAAE,UAAU,EAClB,GAAG,EAAE,qBAAqB,GACzB;IAAE,QAAQ,EAAE,QAAQ,GAAG,SAAS,CAAC;IAAC,QAAQ,EAAE,QAAQ,GAAG,SAAS,CAAA;CAAE,CA8FpE;AAiFD;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,0BAA0B,CACxC,MAAM,EAAE,UAAU,EAClB,GAAG,EAAE,qBAAqB,EAC1B,QAAQ,EAAE,QAAQ,GAAG,SAAS,EAC9B,cAAc,EAAE,MAAM,GACrB,IAAI,CAeN"}
@@ -1,4 +1,4 @@
1
- import { ON_DELETE_DEFAULT_BY_SUBTYPE, ON_UPDATE_DEFAULT, FIELD_ATTR_REQUIRED, IDENTITY_ATTR_FIELDS, TYPE_VALIDATOR, VALIDATOR_SUBTYPE_REQUIRED, refMatchesObject, resolveObjectRef, } from "@metaobjectsdev/metadata";
1
+ import { ON_DELETE_DEFAULT_BY_SUBTYPE, ON_UPDATE_DEFAULT, FIELD_ATTR_REQUIRED, IDENTITY_ATTR_FIELDS, TYPE_VALIDATOR, VALIDATOR_SUBTYPE_REQUIRED, refMatchesObject, resolveObjectRef, resolveRelationshipReference, } from "@metaobjectsdev/metadata";
2
2
  import { SetNullNotNullableError } from "./errors.js";
3
3
  // ---------------------------------------------------------------------------
4
4
  // Shared field helpers — exported for use by expected-schema.ts
@@ -59,7 +59,12 @@ export function isRequired(field) {
59
59
  * omits actions when the DB value is "no-action", so the expected side does the same
60
60
  * to keep round-trip diffs clean.
61
61
  *
62
- * If multiple relationships target the same entity (rare), the first one is used.
62
+ * When more than one relationship targets the same entity (Match.homeTeam /
63
+ * awayTeam both -> Team), each is correlated to its OWN identity.reference via
64
+ * the shared relationship<->reference ladder (#368 round 2) — never the first
65
+ * match. When the correlation is genuinely ambiguous, it contributes nothing
66
+ * rather than an arbitrary — possibly wrong — action (see the tier-2/tier-3
67
+ * bodies below for the exact rules).
63
68
  *
64
69
  * The single `as FkAction` cast in normalize() is safe because REFERENTIAL_ACTIONS
65
70
  * (metadata package) and FkAction (migrate-ts/src/types.ts) are the same four-value
@@ -93,6 +98,18 @@ export function resolveReferentialActions(entity, ref) {
93
98
  // When the target does not resolve (dangling @references — normally a
94
99
  // load error), fall back to the legacy exact-string match so behavior on
95
100
  // partially-valid trees is unchanged.
101
+ //
102
+ // #368 (round 2): `entity` may declare MORE THAN ONE identity.reference
103
+ // onto this same target (Match.homeTeamRef / awayTeamRef both -> Team).
104
+ // Matching `r` on the target alone can't say which of those references
105
+ // `r` supplies actions FOR — every FK past the first silently inherited
106
+ // the first relationship's actions. Resolve it with the INVERSE of the
107
+ // relationship->reference ladder (resolveRelationshipReference): `r`
108
+ // belongs to `ref` iff the ladder, applied to `r`, resolves back to
109
+ // `ref` itself — not merely "resolves to *some* reference on this
110
+ // target". `r` and `ref` are always declared on the same `entity`, the
111
+ // exact shape the ladder is built for, so this is a direct inversion,
112
+ // not a second parallel rule.
96
113
  // (3) Failing that, correlate the REVERSE relationship declared on the
97
114
  // TARGET entity (the documented parent-side authoring shape).
98
115
  let rel = entity.relationships().find((r) => {
@@ -105,7 +122,9 @@ export function resolveReferentialActions(entity, ref) {
105
122
  return objectRef === target;
106
123
  const relOwner = r.parent ?? entity;
107
124
  const relOwnerPkg = relOwner.package ?? relOwner.fileDefaultPackage ?? "";
108
- return refMatchesObject(targetObj, objectRef, relOwnerPkg);
125
+ if (!refMatchesObject(targetObj, objectRef, relOwnerPkg))
126
+ return false;
127
+ return resolveRelationshipReference(entity, r.name, objectRef, r.sourceRefField) === ref;
109
128
  });
110
129
  // When the tier-3 satisfiability guard fires, the reverse relationship's
111
130
  // AUTHORED @onUpdate still applies (only the inferred contributions drop).
@@ -160,9 +179,19 @@ export function resolveReferentialActions(entity, ref) {
160
179
  * same target, the reverse relationship cannot say WHICH FK carries the
161
180
  * ownership edge, so it contributes to none of them (arming every FK could
162
181
  * cascade through an edge the author never designated).
163
- *
164
- * If multiple reverse relationships point back at the entity (rare), the first
165
- * one is used mirroring the tier-2 sibling-relationship rule.
182
+ * - When the TARGET entity declares more than one non-@through relationship
183
+ * back at `entity` (rare e.g. a "posts" composition and a separate
184
+ * "latestPost" association both @objectRef-ing Post), no candidate is
185
+ * preferred: this is the tier-2 ambiguity's mirror image (multiple
186
+ * RELATIONSHIPS rather than multiple REFERENCES), and it cannot be
187
+ * resolved by the relationship->reference ladder — that ladder picks among
188
+ * references declared on the SAME object as the relationship, whereas here
189
+ * the candidate relationships live on `targetObj` while `ref` lives on
190
+ * `entity`, a different object, so the ladder has no candidates to apply
191
+ * to. Previously this used the FIRST match (the same silently-wrong-schema
192
+ * defect as tier 2); it now fails closed like the guard above, once the
193
+ * ambiguity guard above has already established `ref` is the entity's only
194
+ * candidate reference to this target.
166
195
  */
167
196
  function findReverseRelationship(entity, ref, targetObj) {
168
197
  const root = entity.parent;
@@ -184,7 +213,11 @@ function findReverseRelationship(entity, ref, targetObj) {
184
213
  return undefined;
185
214
  // The reverse relationship's bare @objectRef resolves in ITS declaring
186
215
  // owner's package (normally the target entity's own package).
187
- return targetObj.relationships().find((r) => {
216
+ //
217
+ // Fail closed on ambiguity rather than taking the first match: if more than
218
+ // one non-@through relationship on targetObj resolves back to `entity`,
219
+ // none of them is preferred (see the class doc above).
220
+ const reverseCandidates = targetObj.relationships().filter((r) => {
188
221
  if (r.through !== undefined)
189
222
  return false; // M:N — junction path, not this FK
190
223
  const objectRef = r.objectRef;
@@ -194,6 +227,7 @@ function findReverseRelationship(entity, ref, targetObj) {
194
227
  const relOwnerPkg = relOwner.package ?? relOwner.fileDefaultPackage ?? "";
195
228
  return refMatchesObject(entity, objectRef, relOwnerPkg);
196
229
  });
230
+ return reverseCandidates.length === 1 ? reverseCandidates[0] : undefined;
197
231
  }
198
232
  function normalize(a) {
199
233
  if (a === undefined)
@@ -1 +1 @@
1
- {"version":3,"file":"referential-actions.js","sourceRoot":"","sources":["../src/referential-actions.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"referential-actions.js","sourceRoot":"","sources":["../src/referential-actions.ts"],"names":[],"mappings":"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"}
package/dist/scope.d.ts CHANGED
@@ -31,6 +31,16 @@ export interface ScopedExpectedSchema {
31
31
  * (nothing to derive from; `diff`'s legacy whole-DB fallback is preserved).
32
32
  */
33
33
  declaredSchemas?: string[];
34
+ /**
35
+ * The subset of `outOfScope` a DEPENDENCY contributed (FR-023), so a caller can say
36
+ * why an object was excluded. `outOfScope` deliberately stays the FULL set — it is
37
+ * what feeds `unmanagedNames`, and splitting it would silently drop half of the
38
+ * actual-side suppression.
39
+ *
40
+ * `undefined` when no `imported` predicate was supplied, which is every project that
41
+ * declares no dependencies.
42
+ */
43
+ importedOutOfScope?: string[];
34
44
  }
35
45
  /**
36
46
  * Carry an out-of-scope object forward into the snapshot a run is about to commit.
@@ -105,17 +115,35 @@ export declare function scopedDiffInputs(scoped: ScopedExpectedSchema, unmanaged
105
115
  * `diff`'s legacy whole-database fallback for a genuinely empty model.
106
116
  */
107
117
  export declare function declaredSchemasOf(snapshot: SchemaSnapshot): string[];
118
+ /** Optional exclusions layered on top of `inScope`. */
119
+ export interface ScopeExpectedSchemaOptions {
120
+ /**
121
+ * FR-023 — is this object's declaring FQN owned by one of the project's
122
+ * DEPENDENCIES? Such an object is loaded so the consumer's own model can resolve
123
+ * against it, and is governed by nobody here unless the consumer's own
124
+ * `migrate.scope` names its package (which `inScope` then admits).
125
+ *
126
+ * Supplied only by a project that declares dependencies: absent, this function
127
+ * behaves exactly as it always has.
128
+ */
129
+ imported?: ObjectScopePredicate;
130
+ }
108
131
  /**
109
132
  * Narrow an expected schema to the objects inside `inScope`.
110
133
  *
111
- * An undefined predicate returns the input untouched — the SAME snapshot object,
112
- * not an equal copy — so a project that declares no `migrate.scope` reaches the
113
- * diff, the emitter and the committed snapshot through an unchanged value.
134
+ * An undefined predicate with no `opts` returns the input untouched — the SAME
135
+ * snapshot object, not an equal copy — so a project that declares no `migrate.scope`
136
+ * and no dependencies reaches the diff, the emitter and the committed snapshot
137
+ * through an unchanged value.
138
+ *
139
+ * A table or view with NO recorded provenance is KEPT, by both exclusions. Scope
140
+ * decides on the declaring object's FQN, and an object whose FQN is unknown was never
141
+ * proven to be anyone else's; dropping it would silently un-manage it (and, worse,
142
+ * suppressing its name on the actual side would hide real drift).
114
143
  *
115
- * A table or view with NO recorded provenance is KEPT. Scope decides on the
116
- * declaring object's FQN, and an object whose FQN is unknown was never proven to be
117
- * anyone else's; dropping it would silently un-manage it (and, worse, suppressing
118
- * its name on the actual side would hide real drift).
144
+ * With `opts.imported`, an imported object the scope does not admit leaves the
145
+ * expected side BEFORE `declaredSchemas` is computed see the module header for why
146
+ * that ordering is the whole point.
119
147
  */
120
- export declare function scopeExpectedSchema(built: ExpectedSchemaWithProvenance, inScope: ObjectScopePredicate | undefined): ScopedExpectedSchema;
148
+ export declare function scopeExpectedSchema(built: ExpectedSchemaWithProvenance, inScope: ObjectScopePredicate | undefined, opts?: ScopeExpectedSchemaOptions): ScopedExpectedSchema;
121
149
  //# sourceMappingURL=scope.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"scope.d.ts","sourceRoot":"","sources":["../src/scope.ts"],"names":[],"mappings":"AAwCA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,iBAAiB,CAAC;AAChD,OAAO,KAAK,EAAE,4BAA4B,EAAE,MAAM,sBAAsB,CAAC;AAEzE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAEjD;;;;;;GAMG;AACH,MAAM,MAAM,oBAAoB,GAAG,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC;AAE5D,MAAM,WAAW,oBAAoB;IACnC,4DAA4D;IAC5D,QAAQ,EAAE,cAAc,CAAC;IACzB;;;;;OAKG;IACH,UAAU,EAAE,MAAM,EAAE,CAAC;IACrB;;;;;;;;;;OAUG;IACH,eAAe,CAAC,EAAE,MAAM,EAAE,CAAC;CAC5B;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,sBAAsB,CACpC,IAAI,EAAE,cAAc,EACpB,KAAK,EAAE,cAAc,EACrB,UAAU,EAAE,SAAS,MAAM,EAAE,GAC5B,cAAc,CAQhB;AAED;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,wBAAgB,mBAAmB,CACjC,QAAQ,EAAE,cAAc,EACxB,QAAQ,EAAE,aAAa,GACtB,oBAAoB,CAatB;AAED,kEAAkE;AAClE,MAAM,WAAW,aAAa;IAC5B,4EAA4E;IAC5E,QAAQ,CAAC,UAAU,EAAE,SAAS,MAAM,EAAE,CAAC;IACvC,4EAA4E;IAC5E,QAAQ,CAAC,eAAe,CAAC,EAAE,SAAS,MAAM,EAAE,GAAG,SAAS,CAAC;CAC1D;AAoBD;;;;;;;;;;;;;GAaG;AACH,wBAAgB,gBAAgB,CAC9B,MAAM,EAAE,oBAAoB,EAC5B,SAAS,EAAE,SAAS,MAAM,EAAE,GAC3B,IAAI,CAAC,QAAQ,EAAE,UAAU,GAAG,gBAAgB,GAAG,cAAc,CAAC,CAMhE;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,iBAAiB,CAAC,QAAQ,EAAE,cAAc,GAAG,MAAM,EAAE,CAQpE;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,mBAAmB,CACjC,KAAK,EAAE,4BAA4B,EACnC,OAAO,EAAE,oBAAoB,GAAG,SAAS,GACxC,oBAAoB,CA0BtB"}
1
+ {"version":3,"file":"scope.d.ts","sourceRoot":"","sources":["../src/scope.ts"],"names":[],"mappings":"AAoDA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,iBAAiB,CAAC;AAChD,OAAO,KAAK,EAAE,4BAA4B,EAAE,MAAM,sBAAsB,CAAC;AAEzE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAEjD;;;;;;GAMG;AACH,MAAM,MAAM,oBAAoB,GAAG,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC;AAE5D,MAAM,WAAW,oBAAoB;IACnC,4DAA4D;IAC5D,QAAQ,EAAE,cAAc,CAAC;IACzB;;;;;OAKG;IACH,UAAU,EAAE,MAAM,EAAE,CAAC;IACrB;;;;;;;;;;OAUG;IACH,eAAe,CAAC,EAAE,MAAM,EAAE,CAAC;IAC3B;;;;;;;;OAQG;IACH,kBAAkB,CAAC,EAAE,MAAM,EAAE,CAAC;CAC/B;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,sBAAsB,CACpC,IAAI,EAAE,cAAc,EACpB,KAAK,EAAE,cAAc,EACrB,UAAU,EAAE,SAAS,MAAM,EAAE,GAC5B,cAAc,CAQhB;AAED;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,wBAAgB,mBAAmB,CACjC,QAAQ,EAAE,cAAc,EACxB,QAAQ,EAAE,aAAa,GACtB,oBAAoB,CAatB;AAED,kEAAkE;AAClE,MAAM,WAAW,aAAa;IAC5B,4EAA4E;IAC5E,QAAQ,CAAC,UAAU,EAAE,SAAS,MAAM,EAAE,CAAC;IACvC,4EAA4E;IAC5E,QAAQ,CAAC,eAAe,CAAC,EAAE,SAAS,MAAM,EAAE,GAAG,SAAS,CAAC;CAC1D;AAoBD;;;;;;;;;;;;;GAaG;AACH,wBAAgB,gBAAgB,CAC9B,MAAM,EAAE,oBAAoB,EAC5B,SAAS,EAAE,SAAS,MAAM,EAAE,GAC3B,IAAI,CAAC,QAAQ,EAAE,UAAU,GAAG,gBAAgB,GAAG,cAAc,CAAC,CAMhE;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,iBAAiB,CAAC,QAAQ,EAAE,cAAc,GAAG,MAAM,EAAE,CAQpE;AAED,uDAAuD;AACvD,MAAM,WAAW,0BAA0B;IACzC;;;;;;;;OAQG;IACH,QAAQ,CAAC,EAAE,oBAAoB,CAAC;CACjC;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,mBAAmB,CACjC,KAAK,EAAE,4BAA4B,EACnC,OAAO,EAAE,oBAAoB,GAAG,SAAS,EACzC,IAAI,CAAC,EAAE,0BAA0B,GAChC,oBAAoB,CA2DtB"}
package/dist/scope.js CHANGED
@@ -34,6 +34,18 @@
34
34
  // survivors instead is precisely the inversion above. Declaring a scope is not a way
35
35
  // to hand a schema over; removing the objects from the model is.
36
36
  //
37
+ // FR-023 — AN IMPORT IS THE ONE EXCLUSION THAT MUST MOVE THE SCHEMA SET. The rule
38
+ // above is stated for `migrate.scope`, where it is right: that scope narrows a model
39
+ // which DID declare into the schema, so the schema stays this model's to manage. A
40
+ // dependency's node is the opposite case — the consumer never declared it, and the
41
+ // publisher owns the schema it sits in. Leaving that schema pinned would make every
42
+ // table the publisher did NOT export, tables this consumer has never heard of, a
43
+ // proposed DROP against the publisher's data.
44
+ //
45
+ // So `scopeExpectedSchema`'s optional `imported` predicate partitions those objects
46
+ // out FIRST, and `declaredSchemas` is derived from what remains. Everything
47
+ // `migrate.scope` excluded is still in that remainder, so the rule above is untouched.
48
+ //
37
49
  // `scopedDiffInputs` exists so no caller has to remember any of this: it returns all
38
50
  // three obligations as one object, and every scoped `diff` call goes through it.
39
51
  import { DEFAULT_DB_SCHEMA_POSTGRES } from "@metaobjectsdev/metadata";
@@ -155,39 +167,79 @@ export function declaredSchemasOf(snapshot) {
155
167
  /**
156
168
  * Narrow an expected schema to the objects inside `inScope`.
157
169
  *
158
- * An undefined predicate returns the input untouched — the SAME snapshot object,
159
- * not an equal copy — so a project that declares no `migrate.scope` reaches the
160
- * diff, the emitter and the committed snapshot through an unchanged value.
170
+ * An undefined predicate with no `opts` returns the input untouched — the SAME
171
+ * snapshot object, not an equal copy — so a project that declares no `migrate.scope`
172
+ * and no dependencies reaches the diff, the emitter and the committed snapshot
173
+ * through an unchanged value.
174
+ *
175
+ * A table or view with NO recorded provenance is KEPT, by both exclusions. Scope
176
+ * decides on the declaring object's FQN, and an object whose FQN is unknown was never
177
+ * proven to be anyone else's; dropping it would silently un-manage it (and, worse,
178
+ * suppressing its name on the actual side would hide real drift).
161
179
  *
162
- * A table or view with NO recorded provenance is KEPT. Scope decides on the
163
- * declaring object's FQN, and an object whose FQN is unknown was never proven to be
164
- * anyone else's; dropping it would silently un-manage it (and, worse, suppressing
165
- * its name on the actual side would hide real drift).
180
+ * With `opts.imported`, an imported object the scope does not admit leaves the
181
+ * expected side BEFORE `declaredSchemas` is computed see the module header for why
182
+ * that ordering is the whole point.
166
183
  */
167
- export function scopeExpectedSchema(built, inScope) {
168
- if (inScope === undefined)
184
+ export function scopeExpectedSchema(built, inScope, opts) {
185
+ const imported = opts?.imported;
186
+ if (inScope === undefined && imported === undefined) {
169
187
  return { snapshot: built.snapshot, outOfScope: [] };
170
- // Computed from `built.snapshot` — the UNSCOPED side — deliberately, and before
171
- // the filter below runs. Deriving it from the survivors would reproduce exactly
172
- // the defect this exists to close.
173
- const declared = declaredSchemasOf(built.snapshot);
174
- const outOfScope = [];
188
+ }
189
+ // PASS 1 — the import partition. An imported object survives only when the
190
+ // consumer's own scope NAMES it, which is how a consumer opts into owning a
191
+ // dependency's tables (DESIGN §11.1 item 2: "explicit include replaces `mode`").
192
+ //
193
+ // An `inScope` of `undefined` does NOT rescue it. That combination cannot arise
194
+ // from the CLI — `inMigrateScope` is defined whenever dependencies exist — and
195
+ // resolving it the other way would be the unsafe direction: excluding an import
196
+ // costs nothing, while keeping one risks DDL against the publisher's database.
197
+ const importedOutOfScope = [];
198
+ const declaredHere = (obj) => {
199
+ if (imported === undefined)
200
+ return true;
201
+ const qualified = qualifiedDbName(obj);
202
+ const fqn = built.provenance.get(qualified);
203
+ if (fqn === undefined || !imported(fqn))
204
+ return true;
205
+ if (inScope?.(fqn) === true)
206
+ return true;
207
+ importedOutOfScope.push(qualified);
208
+ return false;
209
+ };
210
+ const remainder = {
211
+ ...built.snapshot,
212
+ tables: built.snapshot.tables.filter(declaredHere),
213
+ views: built.snapshot.views.filter(declaredHere),
214
+ };
215
+ // Computed from the REMAINDER — everything this model actually declares — and
216
+ // before the scope filter below. Deriving it from the scope's survivors instead
217
+ // would reproduce exactly the defect the module header describes; deriving it from
218
+ // the unpartitioned side would pin the publisher's schema. Both halves matter.
219
+ const declared = declaredSchemasOf(remainder);
220
+ // PASS 2 — the per-command scope, unchanged.
221
+ const scopeOutOfScope = [];
175
222
  const governed = (obj) => {
223
+ if (inScope === undefined)
224
+ return true;
176
225
  const qualified = qualifiedDbName(obj);
177
226
  const fqn = built.provenance.get(qualified);
178
227
  if (fqn === undefined || inScope(fqn))
179
228
  return true;
180
- outOfScope.push(qualified);
229
+ scopeOutOfScope.push(qualified);
181
230
  return false;
182
231
  };
183
232
  return {
184
233
  snapshot: {
185
- ...built.snapshot,
186
- tables: built.snapshot.tables.filter(governed),
187
- views: built.snapshot.views.filter(governed),
234
+ ...remainder,
235
+ tables: remainder.tables.filter(governed),
236
+ views: remainder.views.filter(governed),
188
237
  },
189
- outOfScope,
238
+ // ONE suppression set: both exclusions must reach `unmanagedNames`, or the
239
+ // objects they removed from `expected` come back as proposed drops.
240
+ outOfScope: [...importedOutOfScope, ...scopeOutOfScope],
190
241
  ...(declared.length > 0 ? { declaredSchemas: declared } : {}),
242
+ ...(imported !== undefined ? { importedOutOfScope } : {}),
191
243
  };
192
244
  }
193
245
  //# sourceMappingURL=scope.js.map
package/dist/scope.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"scope.js","sourceRoot":"","sources":["../src/scope.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"scope.js","sourceRoot":"","sources":["../src/scope.ts"],"names":[],"mappings":"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"}
@@ -18,6 +18,15 @@ export interface PlanOfflineArgs extends Pick<DiffArgs, "allow" | "onAmbiguous"
18
18
  * loaded (unchanged behavior).
19
19
  */
20
20
  inScope?: ObjectScopePredicate;
21
+ /**
22
+ * FR-023 — objects a DEPENDENCY owns. Imported metadata is loaded so this project's
23
+ * own model can resolve against it and is governed by nobody here unless `inScope`
24
+ * admits it, so such an object leaves BOTH sides of the diff and, unlike an
25
+ * `inScope` exclusion, takes its database schema out of the run's scope with it
26
+ * (migrate-ts `scope.ts` header). Omit for a project with no dependencies
27
+ * (unchanged behavior).
28
+ */
29
+ imported?: ObjectScopePredicate;
21
30
  }
22
31
  export interface PlanOfflineResult {
23
32
  /** The change set to emit, from diffing metadata-expected against the snapshot. */
@@ -36,8 +45,12 @@ export interface PlanOfflineResult {
36
45
  * only for what it governs.
37
46
  */
38
47
  expected: SchemaSnapshot;
39
- /** Qualified physical names excluded by `inScope`; empty when no scope was given. */
48
+ /** Qualified physical names excluded by `inScope` OR by `imported`; empty when
49
+ * neither was given. */
40
50
  outOfScope: readonly string[];
51
+ /** The subset of `outOfScope` a dependency contributed (FR-023), so the CLI can say
52
+ * WHY those objects were excluded. `undefined` when no `imported` was given. */
53
+ importedOutOfScope: readonly string[] | undefined;
41
54
  }
42
55
  /**
43
56
  * Plan a migration offline: build the expected schema from metadata and diff it
@@ -1 +1 @@
1
- {"version":3,"file":"plan.d.ts","sourceRoot":"","sources":["../../src/snapshot/plan.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,oBAAoB,EAAE,QAAQ,EAAE,MAAM,0BAA0B,CAAC;AAE/E,OAAO,EAAQ,KAAK,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAEvD,OAAO,EAAiE,KAAK,oBAAoB,EAAE,MAAM,aAAa,CAAC;AACvH,OAAO,KAAK,EAAE,OAAO,EAAE,UAAU,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AACvE,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,uBAAuB,CAAC;AAE/D,MAAM,WAAW,eAAgB,SAAQ,IAAI,CAAC,QAAQ,EAAE,OAAO,GAAG,aAAa,GAAG,cAAc,CAAC;IAC/F,QAAQ,EAAE,QAAQ,CAAC;IACnB,OAAO,EAAE,OAAO,CAAC;IACjB,4GAA4G;IAC5G,QAAQ,EAAE,cAAc,CAAC;IACzB,oBAAoB,CAAC,EAAE,oBAAoB,CAAC;IAC5C,kGAAkG;IAClG,KAAK,CAAC,EAAE,SAAS,iBAAiB,EAAE,CAAC;IACrC;;;;;OAKG;IACH,OAAO,CAAC,EAAE,oBAAoB,CAAC;CAChC;AAED,MAAM,WAAW,iBAAiB;IAChC,mFAAmF;IACnF,IAAI,EAAE,UAAU,CAAC;IACjB;;;;;OAKG;IACH,YAAY,EAAE,cAAc,CAAC;IAC7B;;;;;OAKG;IACH,QAAQ,EAAE,cAAc,CAAC;IACzB,qFAAqF;IACrF,UAAU,EAAE,SAAS,MAAM,EAAE,CAAC;CAC/B;AAED;;;;GAIG;AACH,wBAAsB,WAAW,CAAC,IAAI,EAAE,eAAe,GAAG,OAAO,CAAC,iBAAiB,CAAC,CAiCnF;AAED,8EAA8E;AAC9E,wBAAgB,oBAAoB,CAClC,QAAQ,EAAE,QAAQ,EAClB,OAAO,EAAE,OAAO,EAChB,oBAAoB,CAAC,EAAE,oBAAoB,EAC3C,KAAK,CAAC,EAAE,SAAS,iBAAiB,EAAE,GACnC,cAAc,CAMhB"}
1
+ {"version":3,"file":"plan.d.ts","sourceRoot":"","sources":["../../src/snapshot/plan.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,oBAAoB,EAAE,QAAQ,EAAE,MAAM,0BAA0B,CAAC;AAE/E,OAAO,EAAQ,KAAK,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAEvD,OAAO,EAAiE,KAAK,oBAAoB,EAAE,MAAM,aAAa,CAAC;AACvH,OAAO,KAAK,EAAE,OAAO,EAAE,UAAU,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AACvE,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,uBAAuB,CAAC;AAE/D,MAAM,WAAW,eAAgB,SAAQ,IAAI,CAAC,QAAQ,EAAE,OAAO,GAAG,aAAa,GAAG,cAAc,CAAC;IAC/F,QAAQ,EAAE,QAAQ,CAAC;IACnB,OAAO,EAAE,OAAO,CAAC;IACjB,4GAA4G;IAC5G,QAAQ,EAAE,cAAc,CAAC;IACzB,oBAAoB,CAAC,EAAE,oBAAoB,CAAC;IAC5C,kGAAkG;IAClG,KAAK,CAAC,EAAE,SAAS,iBAAiB,EAAE,CAAC;IACrC;;;;;OAKG;IACH,OAAO,CAAC,EAAE,oBAAoB,CAAC;IAC/B;;;;;;;OAOG;IACH,QAAQ,CAAC,EAAE,oBAAoB,CAAC;CACjC;AAED,MAAM,WAAW,iBAAiB;IAChC,mFAAmF;IACnF,IAAI,EAAE,UAAU,CAAC;IACjB;;;;;OAKG;IACH,YAAY,EAAE,cAAc,CAAC;IAC7B;;;;;OAKG;IACH,QAAQ,EAAE,cAAc,CAAC;IACzB;6BACyB;IACzB,UAAU,EAAE,SAAS,MAAM,EAAE,CAAC;IAC9B;qFACiF;IACjF,kBAAkB,EAAE,SAAS,MAAM,EAAE,GAAG,SAAS,CAAC;CACnD;AAED;;;;GAIG;AACH,wBAAsB,WAAW,CAAC,IAAI,EAAE,eAAe,GAAG,OAAO,CAAC,iBAAiB,CAAC,CAwCnF;AAED,8EAA8E;AAC9E,wBAAgB,oBAAoB,CAClC,QAAQ,EAAE,QAAQ,EAClB,OAAO,EAAE,OAAO,EAChB,oBAAoB,CAAC,EAAE,oBAAoB,EAC3C,KAAK,CAAC,EAAE,SAAS,iBAAiB,EAAE,GACnC,cAAc,CAMhB"}
@@ -12,7 +12,7 @@ export async function planOffline(args) {
12
12
  dialect: args.dialect,
13
13
  ...(args.columnNamingStrategy ? { columnNamingStrategy: args.columnNamingStrategy } : {}),
14
14
  ...(args.views !== undefined ? { views: args.views } : {}),
15
- }), args.inScope);
15
+ }), args.inScope, args.imported !== undefined ? { imported: args.imported } : undefined);
16
16
  // The DIFF runs against the narrowed side; the SNAPSHOT keeps what this run
17
17
  // excluded. Committing the narrowed schema would delete every out-of-scope entry
18
18
  // the previous snapshot held, so removing or widening `migrate.scope` later would
@@ -36,7 +36,13 @@ export async function planOffline(args) {
36
36
  ...(args.onAmbiguous ? { onAmbiguous: args.onAmbiguous } : {}),
37
37
  ...(args.ignoreTables ? { ignoreTables: args.ignoreTables } : {}),
38
38
  });
39
- return { diff: result, nextSnapshot, expected: scoped.snapshot, outOfScope: scoped.outOfScope };
39
+ return {
40
+ diff: result,
41
+ nextSnapshot,
42
+ expected: scoped.snapshot,
43
+ outOfScope: scoped.outOfScope,
44
+ importedOutOfScope: scoped.importedOutOfScope,
45
+ };
40
46
  }
41
47
  /** Seed an initial reference snapshot from metadata (greenfield baseline). */
42
48
  export function baselineFromMetadata(metadata, dialect, columnNamingStrategy, views) {
@@ -1 +1 @@
1
- {"version":3,"file":"plan.js","sourceRoot":"","sources":["../../src/snapshot/plan.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,mBAAmB,EAAE,iCAAiC,EAAE,MAAM,uBAAuB,CAAC;AAC/F,OAAO,EAAE,IAAI,EAAiB,MAAM,kBAAkB,CAAC;AACvD,OAAO,EAAE,qBAAqB,EAAE,MAAM,iBAAiB,CAAC;AACxD,OAAO,EAAE,sBAAsB,EAAE,mBAAmB,EAAE,gBAAgB,EAA6B,MAAM,aAAa,CAAC;AA0CvH;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,WAAW,CAAC,IAAqB;IACrD,MAAM,MAAM,GAAG,mBAAmB,CAChC,iCAAiC,CAAC,IAAI,CAAC,QAAQ,EAAE;QAC/C,OAAO,EAAE,IAAI,CAAC,OAAO;QACrB,GAAG,CAAC,IAAI,CAAC,oBAAoB,CAAC,CAAC,CAAC,EAAE,oBAAoB,EAAE,IAAI,CAAC,oBAAoB,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QACzF,GAAG,CAAC,IAAI,CAAC,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,IAAI,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;KAC3D,CAAC,EACF,IAAI,CAAC,OAAO,CACb,CAAC;IACF,4EAA4E;IAC5E,iFAAiF;IACjF,kFAAkF;IAClF,iFAAiF;IACjF,8DAA8D;IAC9D,MAAM,YAAY,GAAG,sBAAsB,CAAC,MAAM,CAAC,QAAQ,EAAE,IAAI,CAAC,QAAQ,EAAE,MAAM,CAAC,UAAU,CAAC,CAAC;IAC/F,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC;QACxB,8EAA8E;QAC9E,6EAA6E;QAC7E,+EAA+E;QAC/E,8EAA8E;QAC9E,2CAA2C;QAC3C,GAAG,gBAAgB,CAAC,MAAM,EAAE,qBAAqB,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;QACjE,MAAM,EAAE,IAAI,CAAC,QAAQ;QACrB,OAAO,EAAE,IAAI,CAAC,OAAO;QACrB,kFAAkF;QAClF,mFAAmF;QACnF,uFAAuF;QACvF,sBAAsB,EAAE,IAAI;QAC5B,GAAG,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,IAAI,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QAC5C,GAAG,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC,CAAC,EAAE,WAAW,EAAE,IAAI,CAAC,WAAW,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QAC9D,GAAG,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC,CAAC,EAAE,YAAY,EAAE,IAAI,CAAC,YAAY,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;KAClE,CAAC,CAAC;IACH,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,YAAY,EAAE,QAAQ,EAAE,MAAM,CAAC,QAAQ,EAAE,UAAU,EAAE,MAAM,CAAC,UAAU,EAAE,CAAC;AAClG,CAAC;AAED,8EAA8E;AAC9E,MAAM,UAAU,oBAAoB,CAClC,QAAkB,EAClB,OAAgB,EAChB,oBAA2C,EAC3C,KAAoC;IAEpC,OAAO,mBAAmB,CAAC,QAAQ,EAAE;QACnC,OAAO;QACP,GAAG,CAAC,oBAAoB,CAAC,CAAC,CAAC,EAAE,oBAAoB,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QACzD,GAAG,CAAC,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;KAC1C,CAAC,CAAC;AACL,CAAC"}
1
+ {"version":3,"file":"plan.js","sourceRoot":"","sources":["../../src/snapshot/plan.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@metaobjectsdev/migrate-ts",
3
- "version": "1.0.2",
3
+ "version": "1.0.4-rc.1",
4
4
  "description": "Schema migration tooling for MetaObjects: diff metadata vs DB and emit SQL for Postgres and SQLite.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -45,7 +45,7 @@
45
45
  },
46
46
  "dependencies": {
47
47
  "@iarna/toml": "^2.2.5",
48
- "@metaobjectsdev/metadata": "1.0.2"
48
+ "@metaobjectsdev/metadata": "1.0.4-rc.1"
49
49
  },
50
50
  "peerDependencies": {
51
51
  "kysely": ">=0.27.0 <0.30.0",
@@ -58,6 +58,18 @@ export interface ComputeDriftOptions {
58
58
  * failing on tables migrate does not own is incoherent.
59
59
  */
60
60
  inScope?: ObjectScopePredicate;
61
+ /**
62
+ * FR-023 — objects a DEPENDENCY owns. Such an object is loaded so this project's own
63
+ * model can resolve against it and is governed by nobody here unless `inScope`
64
+ * admits it: it leaves the expected side, is suppressed on the actual side, and —
65
+ * unlike an `inScope` exclusion — takes the publisher's database schema out of the
66
+ * run's scope with it, so a table the publisher never exported is not reported as an
67
+ * extra table in a schema this project does not manage (migrate-ts `scope.ts`).
68
+ *
69
+ * `verify --db` and `migrate` share this declaration for the same reason they share
70
+ * `inScope`. Omit for a project with no dependencies (unchanged behavior).
71
+ */
72
+ imported?: ObjectScopePredicate;
61
73
  }
62
74
 
63
75
  export interface DriftResult extends DiffResult {
@@ -77,6 +89,13 @@ export interface DriftResult extends DiffResult {
77
89
  * `GovernedScope`, which is what `excludeFromSnapshot` takes.
78
90
  */
79
91
  declaredSchemas: readonly string[] | undefined;
92
+ /**
93
+ * The subset of `outOfScope` a DEPENDENCY contributed (FR-023). Reported for the
94
+ * same reason `declaredSchemas` is: so the caller states WHY an object was left out
95
+ * without re-deriving the decision from a second expected-schema build, which could
96
+ * come to disagree with the one this comparison actually made.
97
+ */
98
+ importedOutOfScope: readonly string[] | undefined;
80
99
  }
81
100
 
82
101
  /**
@@ -106,6 +125,7 @@ export async function computeDriftFromActual(
106
125
  ...(opts?.views !== undefined ? { views: opts.views } : {}),
107
126
  }),
108
127
  opts?.inScope,
128
+ opts?.imported !== undefined ? { imported: opts.imported } : undefined,
109
129
  );
110
130
  const result = await diff({
111
131
  // The three scoped-diff obligations as one value (see scope.ts's header):
@@ -122,6 +142,7 @@ export async function computeDriftFromActual(
122
142
  ...result,
123
143
  outOfScope: scoped.outOfScope,
124
144
  declaredSchemas: scoped.declaredSchemas,
145
+ importedOutOfScope: scoped.importedOutOfScope,
125
146
  };
126
147
  }
127
148
 
@@ -7,6 +7,7 @@ import {
7
7
  VALIDATOR_SUBTYPE_REQUIRED,
8
8
  refMatchesObject,
9
9
  resolveObjectRef,
10
+ resolveRelationshipReference,
10
11
  type MetaObject,
11
12
  type MetaRelationship,
12
13
  type MetaReferenceIdentity,
@@ -76,7 +77,12 @@ export function isRequired(field: MetaData): boolean {
76
77
  * omits actions when the DB value is "no-action", so the expected side does the same
77
78
  * to keep round-trip diffs clean.
78
79
  *
79
- * If multiple relationships target the same entity (rare), the first one is used.
80
+ * When more than one relationship targets the same entity (Match.homeTeam /
81
+ * awayTeam both -> Team), each is correlated to its OWN identity.reference via
82
+ * the shared relationship<->reference ladder (#368 round 2) — never the first
83
+ * match. When the correlation is genuinely ambiguous, it contributes nothing
84
+ * rather than an arbitrary — possibly wrong — action (see the tier-2/tier-3
85
+ * bodies below for the exact rules).
80
86
  *
81
87
  * The single `as FkAction` cast in normalize() is safe because REFERENTIAL_ACTIONS
82
88
  * (metadata package) and FkAction (migrate-ts/src/types.ts) are the same four-value
@@ -116,6 +122,18 @@ export function resolveReferentialActions(
116
122
  // When the target does not resolve (dangling @references — normally a
117
123
  // load error), fall back to the legacy exact-string match so behavior on
118
124
  // partially-valid trees is unchanged.
125
+ //
126
+ // #368 (round 2): `entity` may declare MORE THAN ONE identity.reference
127
+ // onto this same target (Match.homeTeamRef / awayTeamRef both -> Team).
128
+ // Matching `r` on the target alone can't say which of those references
129
+ // `r` supplies actions FOR — every FK past the first silently inherited
130
+ // the first relationship's actions. Resolve it with the INVERSE of the
131
+ // relationship->reference ladder (resolveRelationshipReference): `r`
132
+ // belongs to `ref` iff the ladder, applied to `r`, resolves back to
133
+ // `ref` itself — not merely "resolves to *some* reference on this
134
+ // target". `r` and `ref` are always declared on the same `entity`, the
135
+ // exact shape the ladder is built for, so this is a direct inversion,
136
+ // not a second parallel rule.
119
137
  // (3) Failing that, correlate the REVERSE relationship declared on the
120
138
  // TARGET entity (the documented parent-side authoring shape).
121
139
  let rel = entity.relationships().find((r) => {
@@ -125,7 +143,8 @@ export function resolveReferentialActions(
125
143
  if (targetObj === undefined) return objectRef === target;
126
144
  const relOwner = r.parent ?? entity;
127
145
  const relOwnerPkg = relOwner.package ?? relOwner.fileDefaultPackage ?? "";
128
- return refMatchesObject(targetObj, objectRef, relOwnerPkg);
146
+ if (!refMatchesObject(targetObj, objectRef, relOwnerPkg)) return false;
147
+ return resolveRelationshipReference(entity, r.name, objectRef, r.sourceRefField) === ref;
129
148
  });
130
149
  // When the tier-3 satisfiability guard fires, the reverse relationship's
131
150
  // AUTHORED @onUpdate still applies (only the inferred contributions drop).
@@ -185,9 +204,19 @@ export function resolveReferentialActions(
185
204
  * same target, the reverse relationship cannot say WHICH FK carries the
186
205
  * ownership edge, so it contributes to none of them (arming every FK could
187
206
  * cascade through an edge the author never designated).
188
- *
189
- * If multiple reverse relationships point back at the entity (rare), the first
190
- * one is used mirroring the tier-2 sibling-relationship rule.
207
+ * - When the TARGET entity declares more than one non-@through relationship
208
+ * back at `entity` (rare e.g. a "posts" composition and a separate
209
+ * "latestPost" association both @objectRef-ing Post), no candidate is
210
+ * preferred: this is the tier-2 ambiguity's mirror image (multiple
211
+ * RELATIONSHIPS rather than multiple REFERENCES), and it cannot be
212
+ * resolved by the relationship->reference ladder — that ladder picks among
213
+ * references declared on the SAME object as the relationship, whereas here
214
+ * the candidate relationships live on `targetObj` while `ref` lives on
215
+ * `entity`, a different object, so the ladder has no candidates to apply
216
+ * to. Previously this used the FIRST match (the same silently-wrong-schema
217
+ * defect as tier 2); it now fails closed like the guard above, once the
218
+ * ambiguity guard above has already established `ref` is the entity's only
219
+ * candidate reference to this target.
191
220
  */
192
221
  function findReverseRelationship(
193
222
  entity: MetaObject,
@@ -211,7 +240,11 @@ function findReverseRelationship(
211
240
 
212
241
  // The reverse relationship's bare @objectRef resolves in ITS declaring
213
242
  // owner's package (normally the target entity's own package).
214
- return targetObj.relationships().find((r) => {
243
+ //
244
+ // Fail closed on ambiguity rather than taking the first match: if more than
245
+ // one non-@through relationship on targetObj resolves back to `entity`,
246
+ // none of them is preferred (see the class doc above).
247
+ const reverseCandidates = targetObj.relationships().filter((r) => {
215
248
  if (r.through !== undefined) return false; // M:N — junction path, not this FK
216
249
  const objectRef = r.objectRef;
217
250
  if (objectRef === undefined) return false;
@@ -219,6 +252,7 @@ function findReverseRelationship(
219
252
  const relOwnerPkg = relOwner.package ?? relOwner.fileDefaultPackage ?? "";
220
253
  return refMatchesObject(entity, objectRef, relOwnerPkg);
221
254
  });
255
+ return reverseCandidates.length === 1 ? reverseCandidates[0] : undefined;
222
256
  }
223
257
 
224
258
  function normalize(a: string | undefined): FkAction | undefined {
package/src/scope.ts CHANGED
@@ -34,6 +34,18 @@
34
34
  // survivors instead is precisely the inversion above. Declaring a scope is not a way
35
35
  // to hand a schema over; removing the objects from the model is.
36
36
  //
37
+ // FR-023 — AN IMPORT IS THE ONE EXCLUSION THAT MUST MOVE THE SCHEMA SET. The rule
38
+ // above is stated for `migrate.scope`, where it is right: that scope narrows a model
39
+ // which DID declare into the schema, so the schema stays this model's to manage. A
40
+ // dependency's node is the opposite case — the consumer never declared it, and the
41
+ // publisher owns the schema it sits in. Leaving that schema pinned would make every
42
+ // table the publisher did NOT export, tables this consumer has never heard of, a
43
+ // proposed DROP against the publisher's data.
44
+ //
45
+ // So `scopeExpectedSchema`'s optional `imported` predicate partitions those objects
46
+ // out FIRST, and `declaredSchemas` is derived from what remains. Everything
47
+ // `migrate.scope` excluded is still in that remainder, so the rule above is untouched.
48
+ //
37
49
  // `scopedDiffInputs` exists so no caller has to remember any of this: it returns all
38
50
  // three obligations as one object, and every scoped `diff` call goes through it.
39
51
 
@@ -74,6 +86,16 @@ export interface ScopedExpectedSchema {
74
86
  * (nothing to derive from; `diff`'s legacy whole-DB fallback is preserved).
75
87
  */
76
88
  declaredSchemas?: string[];
89
+ /**
90
+ * The subset of `outOfScope` a DEPENDENCY contributed (FR-023), so a caller can say
91
+ * why an object was excluded. `outOfScope` deliberately stays the FULL set — it is
92
+ * what feeds `unmanagedNames`, and splitting it would silently drop half of the
93
+ * actual-side suppression.
94
+ *
95
+ * `undefined` when no `imported` predicate was supplied, which is every project that
96
+ * declares no dependencies.
97
+ */
98
+ importedOutOfScope?: string[];
77
99
  }
78
100
 
79
101
  /**
@@ -217,45 +239,98 @@ export function declaredSchemasOf(snapshot: SchemaSnapshot): string[] {
217
239
  ].sort();
218
240
  }
219
241
 
242
+ /** Optional exclusions layered on top of `inScope`. */
243
+ export interface ScopeExpectedSchemaOptions {
244
+ /**
245
+ * FR-023 — is this object's declaring FQN owned by one of the project's
246
+ * DEPENDENCIES? Such an object is loaded so the consumer's own model can resolve
247
+ * against it, and is governed by nobody here unless the consumer's own
248
+ * `migrate.scope` names its package (which `inScope` then admits).
249
+ *
250
+ * Supplied only by a project that declares dependencies: absent, this function
251
+ * behaves exactly as it always has.
252
+ */
253
+ imported?: ObjectScopePredicate;
254
+ }
255
+
220
256
  /**
221
257
  * Narrow an expected schema to the objects inside `inScope`.
222
258
  *
223
- * An undefined predicate returns the input untouched — the SAME snapshot object,
224
- * not an equal copy — so a project that declares no `migrate.scope` reaches the
225
- * diff, the emitter and the committed snapshot through an unchanged value.
259
+ * An undefined predicate with no `opts` returns the input untouched — the SAME
260
+ * snapshot object, not an equal copy — so a project that declares no `migrate.scope`
261
+ * and no dependencies reaches the diff, the emitter and the committed snapshot
262
+ * through an unchanged value.
263
+ *
264
+ * A table or view with NO recorded provenance is KEPT, by both exclusions. Scope
265
+ * decides on the declaring object's FQN, and an object whose FQN is unknown was never
266
+ * proven to be anyone else's; dropping it would silently un-manage it (and, worse,
267
+ * suppressing its name on the actual side would hide real drift).
226
268
  *
227
- * A table or view with NO recorded provenance is KEPT. Scope decides on the
228
- * declaring object's FQN, and an object whose FQN is unknown was never proven to be
229
- * anyone else's; dropping it would silently un-manage it (and, worse, suppressing
230
- * its name on the actual side would hide real drift).
269
+ * With `opts.imported`, an imported object the scope does not admit leaves the
270
+ * expected side BEFORE `declaredSchemas` is computed see the module header for why
271
+ * that ordering is the whole point.
231
272
  */
232
273
  export function scopeExpectedSchema(
233
274
  built: ExpectedSchemaWithProvenance,
234
275
  inScope: ObjectScopePredicate | undefined,
276
+ opts?: ScopeExpectedSchemaOptions,
235
277
  ): ScopedExpectedSchema {
236
- if (inScope === undefined) return { snapshot: built.snapshot, outOfScope: [] };
278
+ const imported = opts?.imported;
279
+ if (inScope === undefined && imported === undefined) {
280
+ return { snapshot: built.snapshot, outOfScope: [] };
281
+ }
282
+
283
+ // PASS 1 — the import partition. An imported object survives only when the
284
+ // consumer's own scope NAMES it, which is how a consumer opts into owning a
285
+ // dependency's tables (DESIGN §11.1 item 2: "explicit include replaces `mode`").
286
+ //
287
+ // An `inScope` of `undefined` does NOT rescue it. That combination cannot arise
288
+ // from the CLI — `inMigrateScope` is defined whenever dependencies exist — and
289
+ // resolving it the other way would be the unsafe direction: excluding an import
290
+ // costs nothing, while keeping one risks DDL against the publisher's database.
291
+ const importedOutOfScope: string[] = [];
292
+ const declaredHere = <T extends { name: string; schema?: string }>(obj: T): boolean => {
293
+ if (imported === undefined) return true;
294
+ const qualified = qualifiedDbName(obj);
295
+ const fqn = built.provenance.get(qualified);
296
+ if (fqn === undefined || !imported(fqn)) return true;
297
+ if (inScope?.(fqn) === true) return true;
298
+ importedOutOfScope.push(qualified);
299
+ return false;
300
+ };
301
+ const remainder: SchemaSnapshot = {
302
+ ...built.snapshot,
303
+ tables: built.snapshot.tables.filter(declaredHere),
304
+ views: built.snapshot.views.filter(declaredHere),
305
+ };
237
306
 
238
- // Computed from `built.snapshot`the UNSCOPED sidedeliberately, and before
239
- // the filter below runs. Deriving it from the survivors would reproduce exactly
240
- // the defect this exists to close.
241
- const declared = declaredSchemasOf(built.snapshot);
307
+ // Computed from the REMAINDER everything this model actually declares — and
308
+ // before the scope filter below. Deriving it from the scope's survivors instead
309
+ // would reproduce exactly the defect the module header describes; deriving it from
310
+ // the unpartitioned side would pin the publisher's schema. Both halves matter.
311
+ const declared = declaredSchemasOf(remainder);
242
312
 
243
- const outOfScope: string[] = [];
313
+ // PASS 2 the per-command scope, unchanged.
314
+ const scopeOutOfScope: string[] = [];
244
315
  const governed = <T extends { name: string; schema?: string }>(obj: T): boolean => {
316
+ if (inScope === undefined) return true;
245
317
  const qualified = qualifiedDbName(obj);
246
318
  const fqn = built.provenance.get(qualified);
247
319
  if (fqn === undefined || inScope(fqn)) return true;
248
- outOfScope.push(qualified);
320
+ scopeOutOfScope.push(qualified);
249
321
  return false;
250
322
  };
251
323
 
252
324
  return {
253
325
  snapshot: {
254
- ...built.snapshot,
255
- tables: built.snapshot.tables.filter(governed),
256
- views: built.snapshot.views.filter(governed),
326
+ ...remainder,
327
+ tables: remainder.tables.filter(governed),
328
+ views: remainder.views.filter(governed),
257
329
  },
258
- outOfScope,
330
+ // ONE suppression set: both exclusions must reach `unmanagedNames`, or the
331
+ // objects they removed from `expected` come back as proposed drops.
332
+ outOfScope: [...importedOutOfScope, ...scopeOutOfScope],
259
333
  ...(declared.length > 0 ? { declaredSchemas: declared } : {}),
334
+ ...(imported !== undefined ? { importedOutOfScope } : {}),
260
335
  };
261
336
  }
@@ -22,6 +22,15 @@ export interface PlanOfflineArgs extends Pick<DiffArgs, "allow" | "onAmbiguous"
22
22
  * loaded (unchanged behavior).
23
23
  */
24
24
  inScope?: ObjectScopePredicate;
25
+ /**
26
+ * FR-023 — objects a DEPENDENCY owns. Imported metadata is loaded so this project's
27
+ * own model can resolve against it and is governed by nobody here unless `inScope`
28
+ * admits it, so such an object leaves BOTH sides of the diff and, unlike an
29
+ * `inScope` exclusion, takes its database schema out of the run's scope with it
30
+ * (migrate-ts `scope.ts` header). Omit for a project with no dependencies
31
+ * (unchanged behavior).
32
+ */
33
+ imported?: ObjectScopePredicate;
25
34
  }
26
35
 
27
36
  export interface PlanOfflineResult {
@@ -41,8 +50,12 @@ export interface PlanOfflineResult {
41
50
  * only for what it governs.
42
51
  */
43
52
  expected: SchemaSnapshot;
44
- /** Qualified physical names excluded by `inScope`; empty when no scope was given. */
53
+ /** Qualified physical names excluded by `inScope` OR by `imported`; empty when
54
+ * neither was given. */
45
55
  outOfScope: readonly string[];
56
+ /** The subset of `outOfScope` a dependency contributed (FR-023), so the CLI can say
57
+ * WHY those objects were excluded. `undefined` when no `imported` was given. */
58
+ importedOutOfScope: readonly string[] | undefined;
46
59
  }
47
60
 
48
61
  /**
@@ -58,6 +71,7 @@ export async function planOffline(args: PlanOfflineArgs): Promise<PlanOfflineRes
58
71
  ...(args.views !== undefined ? { views: args.views } : {}),
59
72
  }),
60
73
  args.inScope,
74
+ args.imported !== undefined ? { imported: args.imported } : undefined,
61
75
  );
62
76
  // The DIFF runs against the narrowed side; the SNAPSHOT keeps what this run
63
77
  // excluded. Committing the narrowed schema would delete every out-of-scope entry
@@ -82,7 +96,13 @@ export async function planOffline(args: PlanOfflineArgs): Promise<PlanOfflineRes
82
96
  ...(args.onAmbiguous ? { onAmbiguous: args.onAmbiguous } : {}),
83
97
  ...(args.ignoreTables ? { ignoreTables: args.ignoreTables } : {}),
84
98
  });
85
- return { diff: result, nextSnapshot, expected: scoped.snapshot, outOfScope: scoped.outOfScope };
99
+ return {
100
+ diff: result,
101
+ nextSnapshot,
102
+ expected: scoped.snapshot,
103
+ outOfScope: scoped.outOfScope,
104
+ importedOutOfScope: scoped.importedOutOfScope,
105
+ };
86
106
  }
87
107
 
88
108
  /** Seed an initial reference snapshot from metadata (greenfield baseline). */