@vev-software/atlas-contracts 0.1.5 → 0.1.7

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/README.md CHANGED
@@ -23,6 +23,12 @@ import type { LandscapeDocument, Asset } from "@vev-software/atlas-contracts";
23
23
 
24
24
  Ships type declarations (`dist/index.d.ts`) and ESM; no runtime dependencies.
25
25
 
26
+ Digest v1 remains unchanged. `LandscapeDigestV2` adds required per-digest refs,
27
+ optional layer and default-off closed relations. After validating raw JSON against
28
+ `schemas/v2/landscape-digest.schema.json`, call `validateLandscapeDigestV2` for
29
+ cross-item rules. Scope never authorizes sharing; `integrates` requires separate
30
+ stage 2 consent. Both schema versions and their relative dependencies are shipped.
31
+
26
32
  ## Versioning
27
33
 
28
34
  [SemVer](https://semver.org), derived from the git tag (`vX.Y.Z`) at release. Pre-1.0, the v1
package/dist/index.d.ts CHANGED
@@ -393,6 +393,36 @@ export interface LandscapeDigest {
393
393
  scope: LandscapeDigestScope;
394
394
  items: LandscapeDigestItem[];
395
395
  }
396
+ export declare const LANDSCAPE_DIGEST_V2_VERSION: "2";
397
+ export type DigestLayer = "business" | "application" | "data" | "infrastructure";
398
+ /** integrates is contract-only stage 2, requiring separate explicit consent. */
399
+ export type DigestRelationKind = "runsOn" | "suppliedBy" | "integrates";
400
+ /** Signed inclusion scope; omitted relations means false. Never authorization. */
401
+ export interface LandscapeDigestScopeV2 extends LandscapeDigestScope {
402
+ relations?: boolean;
403
+ }
404
+ export interface LandscapeDigestItemV2 extends Omit<LandscapeDigestItem, "plannedChange" | "integrationCount"> {
405
+ /** Required, opaque, 1–64 characters; minted per digest, never a stable asset id. */
406
+ ref: string;
407
+ layer?: DigestLayer;
408
+ plannedChange?: PlannedChange;
409
+ integrationCount?: number;
410
+ }
411
+ /** Closed edge shape: JSON Schema rejects every additional property. */
412
+ export interface LandscapeDigestRelation {
413
+ from: string;
414
+ to: string;
415
+ kind: DigestRelationKind;
416
+ }
417
+ export interface LandscapeDigestV2 extends Omit<LandscapeDigest, "contractVersion" | "scope" | "items"> {
418
+ contractVersion: typeof LANDSCAPE_DIGEST_V2_VERSION;
419
+ scope: LandscapeDigestScopeV2;
420
+ items: LandscapeDigestItemV2[];
421
+ /** Omit entirely unless scope.relations is true, including when empty. */
422
+ relations?: LandscapeDigestRelation[];
423
+ }
424
+ /** Run after JSON Schema validation. Does not grant sharing or consumption permission. */
425
+ export declare function validateLandscapeDigestV2(digest: LandscapeDigestV2): string[];
396
426
  export type TargetScopeKind = "estate" | "domain" | "capability-area";
397
427
  export type TargetVersionStatus = "draft" | "proposed" | "approved" | "active" | "superseded" | "rejected" | "archived";
398
428
  export type ChangeIntent = "add" | "change" | "retire";
package/dist/index.js CHANGED
@@ -5,3 +5,46 @@
5
5
  */
6
6
  /** The atlas-contracts schema major version. */
7
7
  export const CONTRACT_VERSION = "1";
8
+ // --- Landscape share digest v2 (v1 remains unchanged) ---
9
+ export const LANDSCAPE_DIGEST_V2_VERSION = "2";
10
+ /** Run after JSON Schema validation. Does not grant sharing or consumption permission. */
11
+ export function validateLandscapeDigestV2(digest) {
12
+ const errors = [];
13
+ const items = new Map();
14
+ for (const item of digest.items) {
15
+ if (items.has(item.ref))
16
+ errors.push("duplicate-ref");
17
+ items.set(item.ref, item);
18
+ if (!digest.scope.kinds.includes(item.kind))
19
+ errors.push("item-outside-scope");
20
+ }
21
+ if (digest.relations === undefined)
22
+ return errors;
23
+ if (digest.scope.relations !== true)
24
+ errors.push("relations-not-enabled");
25
+ if (digest.relations.length > 20000)
26
+ errors.push("too-many-relations");
27
+ const edges = new Set();
28
+ const isSystem = (kind) => kind === "system" || kind === "application";
29
+ for (const edge of digest.relations) {
30
+ if (edge.from === edge.to)
31
+ errors.push("self-edge");
32
+ const endpoints = edge.kind === "integrates" ? [edge.from, edge.to].sort() : [edge.from, edge.to];
33
+ const key = JSON.stringify([edge.kind, ...endpoints]);
34
+ if (edges.has(key))
35
+ errors.push("duplicate-edge");
36
+ edges.add(key);
37
+ const source = items.get(edge.from);
38
+ const target = items.get(edge.to);
39
+ if (!source || !target) {
40
+ errors.push("dangling-ref");
41
+ continue;
42
+ }
43
+ const valid = edge.kind === "runsOn" ? isSystem(source.kind) && target.kind === "platform"
44
+ : edge.kind === "suppliedBy" ? source.kind !== "vendor" && target.kind === "vendor"
45
+ : edge.kind === "integrates" && isSystem(source.kind) && isSystem(target.kind);
46
+ if (!valid)
47
+ errors.push("invalid-relation-endpoints");
48
+ }
49
+ return errors;
50
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vev-software/atlas-contracts",
3
- "version": "0.1.5",
3
+ "version": "0.1.7",
4
4
  "description": "Public, versioned data model and interop contracts for VEV Atlas — the catalogue asset model plus manual relationships, tags and the landscape import/export document.",
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",
@@ -16,6 +16,7 @@
16
16
  },
17
17
  "scripts": {
18
18
  "build": "tsc -p tsconfig.json",
19
+ "test": "npm run build && tsc --ignoreConfig --noEmit --strict --target ES2022 --module nodenext test/landscape-digest-v2.types.ts && node --test test/*.test.mjs",
19
20
  "prepack": "node scripts/prepack.mjs"
20
21
  },
21
22
  "devDependencies": {
@@ -0,0 +1,73 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "https://contracts.vev.software/atlas/v2/landscape-digest.schema.json",
4
+ "title": "Atlas landscape share digest (v2)",
5
+ "description": "Minimized push-only digest with per-digest refs, optional layer and opt-in relations. Payload scope records sender intent, never authorization. Cross-item invariants require semantic validation; see docs/landscape-digest.md.",
6
+ "type": "object",
7
+ "additionalProperties": false,
8
+ "required": ["contractVersion", "digestId", "generatedAt", "sourceInstanceId", "sequence", "scope", "items"],
9
+ "properties": {
10
+ "contractVersion": { "const": "2" },
11
+ "digestId": { "$ref": "../v1/landscape-digest.schema.json#/properties/digestId" },
12
+ "generatedAt": { "$ref": "../v1/landscape-digest.schema.json#/properties/generatedAt" },
13
+ "sourceInstanceId": { "$ref": "../v1/landscape-digest.schema.json#/properties/sourceInstanceId" },
14
+ "sequence": { "$ref": "../v1/landscape-digest.schema.json#/properties/sequence" },
15
+ "scope": { "$ref": "#/$defs/Scope" },
16
+ "items": { "type": "array", "items": { "$ref": "#/$defs/Item" } },
17
+ "relations": {
18
+ "type": "array",
19
+ "maxItems": 20000,
20
+ "uniqueItems": true,
21
+ "items": { "$ref": "#/$defs/Relation" }
22
+ }
23
+ },
24
+ "if": { "required": ["relations"] },
25
+ "then": {
26
+ "properties": {
27
+ "scope": { "required": ["relations"], "properties": { "relations": { "const": true } } }
28
+ }
29
+ },
30
+ "$defs": {
31
+ "Ref": {
32
+ "type": "string",
33
+ "minLength": 1,
34
+ "maxLength": 64,
35
+ "description": "Opaque, unique within this digest and meaningless outside it. Mint anew per digest; never an asset id or stable identifier."
36
+ },
37
+ "Scope": {
38
+ "type": "object",
39
+ "additionalProperties": false,
40
+ "required": ["kinds", "tags"],
41
+ "properties": {
42
+ "kinds": { "$ref": "../v1/landscape-digest.schema.json#/$defs/DigestScope/properties/kinds" },
43
+ "tags": { "$ref": "../v1/landscape-digest.schema.json#/$defs/DigestScope/properties/tags" },
44
+ "relations": { "type": "boolean", "default": false }
45
+ }
46
+ },
47
+ "Item": {
48
+ "type": "object",
49
+ "additionalProperties": false,
50
+ "required": ["ref", "kind", "name", "lifecycle"],
51
+ "properties": {
52
+ "ref": { "$ref": "#/$defs/Ref" },
53
+ "kind": { "$ref": "../v1/landscape-digest.schema.json#/$defs/DigestKind" },
54
+ "name": { "$ref": "../v1/landscape-digest.schema.json#/$defs/DigestItem/properties/name" },
55
+ "lifecycle": { "$ref": "../v1/common.schema.json#/$defs/Lifecycle" },
56
+ "vendor": { "$ref": "../v1/landscape-digest.schema.json#/$defs/DigestItem/properties/vendor" },
57
+ "plannedChange": { "$ref": "../v1/landscape-digest.schema.json#/$defs/PlannedChange" },
58
+ "integrationCount": { "$ref": "../v1/landscape-digest.schema.json#/$defs/DigestItem/properties/integrationCount" },
59
+ "layer": { "enum": ["business", "application", "data", "infrastructure"] }
60
+ }
61
+ },
62
+ "Relation": {
63
+ "type": "object",
64
+ "additionalProperties": false,
65
+ "required": ["from", "to", "kind"],
66
+ "properties": {
67
+ "from": { "$ref": "#/$defs/Ref" },
68
+ "to": { "$ref": "#/$defs/Ref" },
69
+ "kind": { "enum": ["runsOn", "suppliedBy", "integrates"] }
70
+ }
71
+ }
72
+ }
73
+ }