Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 37 additions & 0 deletions .changeset/metadata-service-getobject-effective-object.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
---
'@objectstack/spec': patch
---

**`IMetadataService.getObject` now declares what it answers with (#6505).**

`getObject` was the only member of the convenience family without a stated
relationship to the generic reader it wraps — `getView` and `getDashboard` both
say "Equivalent to `get('view'|'dashboard', name)`", `getObject` said nothing.
Consumers paid for that silence: #6055 needed the ADR-0110 D3 verdict for the
`objectstack://objects/{objectName}` MCP resource, could not presume
`getObject(name)` and `get('object', name)` resolve the same item, and bought a
second read on the miss path rather than invent the equivalence at a consumer
(Prime Directive #12).

Two facts are now written on the member, both measured against `main`:

- **The equivalence holds.** Every implementation the repo ships resolves the
pair through one lookup. `MetadataManager.getObject` delegates to its own
`get`; `createMemoryMetadata` reads the same `object` map from both; and
`MetadataFacade.getObject` calls `SchemaRegistry.getObject`, which is also
what `SchemaRegistry.getItem('object', …)` — and therefore the facade's own
`get` — special-cases to. On the facade the two return the *identical object
reference*, and both answer `undefined` on a miss.
- **What the pair answers is the runtime-effective object**, not the stored
document. On a `SchemaRegistry`-backed host the answer has been through the
registry's materialization seam (system columns, primary-title designation,
protection/provenance stamping) and carries every `extend` contribution from
other packages. On a plain-store host there is no contribution layer, so the
effective object and the stored item coincide — which is exactly why a
consumer must read the answer as the effective object on both, instead of
inferring the stored one from whichever host it happens to run on. The raw
per-package contributions are not reachable through this contract at all.

Documentation only — no implementation changed, and the doc comment ships in the
package's `.d.ts`. The effective-over-stored rule is the contract-layer
statement of the same fork #6562 rules on for the HTTP meta read surface.
45 changes: 43 additions & 2 deletions packages/spec/src/contracts/metadata-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -327,9 +327,50 @@ export interface IMetadataService {
listNames(type: string): Promise<string[]>;

/**
* Convenience: get an object definition by name
* Convenience: get an object definition by name.
* Equivalent to `get('object', name)` — the same relationship the optional
* members of this family declare ({@link getView}, {@link getDashboard}).
* Every implementation this repo ships resolves the pair through one
* lookup: `MetadataManager.getObject` delegates to its own `get`;
* `createMemoryMetadata` reads the same `object` map from both; and
* `MetadataFacade.getObject` calls `SchemaRegistry.getObject`, which is
* also what `SchemaRegistry.getItem('object', …)` — and therefore the
* facade's own `get` — special-cases to, so both members hand back the
* identical object.
*
* What that lookup ANSWERS is the **runtime-effective** object: the object
* as the engine runs it, not the document its author wrote. On a
* `SchemaRegistry`-backed host the difference is material. The answer is
* the owning package's definition after the registry's materialization
* seam has run — system-column injection, primary-title designation,
* protection/provenance stamping — with every `extend` contribution from
* other packages merged in (`registerObject` → `resolveObject`). An object
* authored with one field comes back carrying the audit and ownership
* columns, `organization_id` on a multi-tenant host, a resolved
* `nameField`, and the extenders' fields. On a plain-store host
* (`MetadataManager`, `createMemoryMetadata`) there is no contribution
* layer to fold, so the effective object and the stored item coincide.
*
* Read the answer as the effective object in **both** cases. A consumer
* that needs the raw stored document — provenance, diffing an authored file
* against what is installed, round-tripping an edit — must not take this
* for one, and `get('object', name)` is not the escape hatch either: on a
* registry-backed host the per-package contributions are reachable only
* through `SchemaRegistry` itself, never through this contract.
*
* `undefined` carries the same ambiguity {@link get} documents — prefer
* {@link getDiagnosed} wherever "absent" and "could not be read" would lead
* to different decisions (#5840).
*
* [#6505] Written down because the silence was being paid for: #6055
* declined to swap this resolver for `getDiagnosed('object', name)` and
* bought a second read on the miss path instead, rather than presume an
* equivalence the contract never made (Prime Directive #12). The
* effective-over-stored half is the contract-layer statement of the fork
* #6562 rules on for the HTTP meta read surface.
*
* @param name - Object name (snake_case)
* @returns The object definition, or undefined if not found
* @returns The runtime-effective object definition, or undefined if not found
*/
getObject(name: string): Promise<unknown | undefined>;

Expand Down
Loading