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
8 changes: 8 additions & 0 deletions .changeset/layered-interface-member.md
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
---
"@objectstack/spec": minor
"@objectstack/metadata-protocol": patch
---

Declare `MetadataProtocol.getMetaItemLayered` — the layered three-way diagnostic read (`GET /api/v1/meta/:type/:name/layers`) now appears on the protocol interface, typed against the already-declared `GetMetaItemLayeredRequestSchema` / `GetMetaItemLayeredResponseSchema`, so callers no longer reach the verb through `any`. Declared optional like its `getMetaItemCached` / `deleteMetaItem` siblings: a declared-surface catch-up to a shipped verb, not a new capability.

In `@objectstack/metadata-protocol`, the implementation's inline return-type annotation for `getMetaItemLayered` drops its dead `'overlay'` arm on `lockSource` and annotates with `MetadataLockSource` directly — the only producer feeding that field on the layered read path is `resolveLockState`, whose return is already typed `MetadataLockSource | undefined` (`'artifact' | 'package' | 'env-forced'`); the `'overlay'` literal in the file belongs to `getEffectiveLock`, a write/delete-door helper that never feeds this response. Type-level change only; no runtime behaviour or wire vocabulary changes.
10 changes: 9 additions & 1 deletion packages/metadata-protocol/src/protocol.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -73,6 +73,7 @@ import {
evaluateLockForDelete,
resolveLockState,
type MetadataLock,
type MetadataLockSource,
type MetadataProvenance,
} from '@objectstack/spec/kernel';
import { validateObjectNamespacePrefix, deriveNamespaceFromPackageId } from '@objectstack/spec/kernel';
Expand DownExpand Up@@ -6081,7 +6082,14 @@ export class ObjectStackProtocolImplementation implements
// ── ADR-0010 protection envelope ──
lock: MetadataLock;
lockReason?: string;
lockSource?: 'artifact' | 'package' | 'env-forced' | 'overlay';
// `MetadataLockSource` (artifact | package | env-forced) — the only
// producer feeding this field on this path is `resolveLockState`,
// whose return is typed `MetadataLockSource | undefined`. The
// `'overlay'` arm this annotation used to carry was dead: the one
// `lockSource: 'overlay'` producer in this file belongs to
// `getEffectiveLock`, a write/delete-door helper that never feeds
// this response (#9740).
lockSource?: MetadataLockSource;
lockDocsUrl?: string;
provenance?: MetadataProvenance;
packageId?: string;
Expand Down
34 changes: 34 additions & 0 deletions packages/spec/src/api/protocol.test.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -59,6 +59,12 @@ import {
GetFieldLabelsResponseSchema,
} from './protocol.zod';
import type { ListNotificationsRequest } from './protocol.zod';
import { expectTypeOf } from 'vitest';
import type {
MetadataProtocol,
GetMetaItemLayeredRequest,
GetMetaItemLayeredResponse,
} from './protocol.zod';

describe('ObjectStack Protocol', () => {

Expand DownExpand Up@@ -1350,3 +1356,31 @@ describe('meta-read request schemas declare organizationId (#9726 — declared =
expect(GetMetaItemLayeredRequestSchema.safeParse({ name: 'account_list' }).success).toBe(false);
});
});

describe('MetadataProtocol declares getMetaItemLayered (#9740)', () => {
// Type-level pins (compiled by the spec test typecheck, the
// translation-typegen.test.ts pattern). The member is an interface
// declaration with no runtime shadow, so its presence and shape are only
// observable to tsc — these pins are what turns red if the member is
// dropped again or drifts off the layered schemas.

it('declares the member optional, against the layered request/response schemas', () => {
// Optional like its `getMetaItemCached` / `deleteMetaItem` siblings:
// additive to a shipped contract, implementation predating declaration.
expectTypeOf<MetadataProtocol['getMetaItemLayered']>().toEqualTypeOf<
((request: GetMetaItemLayeredRequest) => Promise<GetMetaItemLayeredResponse>) | undefined
>();
});

it("keeps the layered lockSource vocabulary closed — no 'overlay' arm", () => {
// The drift this card closed: the implementation's inline annotation
// carried an 'overlay' arm no producer on the layered read path can emit
// (the only `lockSource: 'overlay'` producer is `getEffectiveLock`, a
// write/delete-door helper). The declared wire enum stays
// artifact | package | env-forced; widening it without a producer is the
// declared-but-unenforced direction Prime Directive #10 forbids.
expectTypeOf<GetMetaItemLayeredResponse['lockSource']>().toEqualTypeOf<
'artifact' | 'package' | 'env-forced' | undefined
>();
});
});
22 changes: 11 additions & 11 deletions packages/spec/src/api/protocol.zod.ts
Original file line numberDiff line numberDiff line change
Expand Up@@ -2337,17 +2337,17 @@ export interface MetadataProtocol {
*/
deleteMetaItem?(request: DeleteMetaItemRequest): Promise<DeleteMetaItemResponse>;
getMetaItemCached?(request: GetMetaItemCachedRequest): Promise<GetMetaItemCachedResponse>;
// `getMetaItemLayered` is deliberately NOT declared here yet, although both
// its request and response schemas are (see them above). Declaring the
// member makes tsc check `metadata-protocol`'s implementation against
// `GetMetaItemLayeredResponse`, and that check currently fails on one
// member: the implementation's inline return type annotates `lockSource`
// with an `'overlay'` arm that its only producer (`resolveLockState`, whose
// return is typed `MetadataLockSource | undefined`) can never emit — an
// over-wide annotation, not enforced behaviour. Until that annotation is
// corrected at the implementation, adding the member here would either lie
// about the wire enum or hard-break the implementing class. Tracked as its
// own card (filed from #9726).
/**
* Three-layer diagnostic read (`GET /api/v1/meta/:type/:name/layers`) —
* packaged baseline, tenant overlay row and merged result side by side; see
* {@link GetMetaItemLayeredResponseSchema} for the shape and its #5882
* route-separation rationale. A declared-surface catch-up, not a new
* capability: `metadata-protocol` has shipped this verb since the layered
* route landed. Declared optional like its `getMetaItemCached` /
* `deleteMetaItem` siblings — additive to a shipped contract, with the
* implementation predating the declaration.
*/
getMetaItemLayered?(request: GetMetaItemLayeredRequest): Promise<GetMetaItemLayeredResponse>;
getUiView?(request: GetUiViewRequest): Promise<GetUiViewResponse>;
}

Expand Down
Loading