easyeda-mcp-pro 0.35.2 → 0.35.4

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
@@ -42,8 +42,9 @@
42
42
  </a>
43
43
  </p>
44
44
 
45
+ [![Ask DeepWiki](https://deepwiki.com/badge.svg)](https://deepwiki.com/oaslananka/easyeda-mcp-pro)
46
+
45
47
  <p>
46
- <a href="https://deepwiki.com/oaslananka/easyeda-mcp-pro">Ask DeepWiki</a> ·
47
48
  <a href="docs/ROADMAP.md">Roadmap</a> ·
48
49
  <a href="docs/OPENSSF_BEST_PRACTICES.md">OpenSSF evidence</a> ·
49
50
  <a href="docs/SECURITY_ASSURANCE_CASE.md">Security assurance case</a>
@@ -110,7 +111,7 @@ For advanced configurations, manual instructions, and specific clients, see [Ins
110
111
 
111
112
  ## Overview
112
113
 
113
- easyeda-mcp-pro is a [Model Context Protocol (MCP)](https://modelcontextprotocol.io) server that bridges AI assistants with hardware design workflows in EasyEDA Pro. It exposes up to 60 profile-gated MCP tools for schematic inspection and editing, controlled EasyEDA Pro API calls, BOM management, design rule checks, PCB board analysis, fabrication exports, diagnostics, and supplier integration.
114
+ easyeda-mcp-pro is a [Model Context Protocol (MCP)](https://modelcontextprotocol.io) server that bridges AI assistants with hardware design workflows in EasyEDA Pro. It exposes up to 115 profile-gated MCP tools for schematic inspection and editing, controlled EasyEDA Pro API calls, BOM management, design rule checks, PCB board analysis, fabrication exports, diagnostics, and supplier integration.
114
115
 
115
116
  The server connects to EasyEDA Pro via a WebSocket bridge extension, enabling real-time access to open project data. It integrates with JLCPCB, LCSC, Mouser, and DigiKey for BOM sourcing and pricing.
116
117
 
@@ -498,18 +499,23 @@ Enable suppliers by setting their credentials. All suppliers are disabled by def
498
499
  - **Mouser**: `MOUSER_ENABLED=true` + API key
499
500
  - **DigiKey**: `DIGIKEY_ENABLED=true` + OAuth2 client ID/secret
500
501
 
501
- ### AI Assistance (experimental)
502
+ ### Reserved AI configuration
503
+
504
+ No in-process AI provider client is currently implemented. The `AI_*` variables remain accepted for
505
+ configuration compatibility but are reported as `reserved`, are always ineffective, and must not be
506
+ used to infer that the server sends design data to an AI provider. Do not supply an API key.
502
507
 
503
- Configure an AI provider for LLM-assisted design review:
508
+ | Variable | Default | Current behavior |
509
+ | --------------------------- | ------- | ----------------------------------------------------- |
510
+ | `AI_PROVIDER` | `none` | Reserved; no provider client is invoked |
511
+ | `AI_MODEL` | `''` | Reserved; no model is selected |
512
+ | `AI_API_KEY` | `''` | Reserved; no credential consumer exists |
513
+ | `AI_MAX_TOKENS` | `8000` | Reserved compatibility setting |
514
+ | `AI_TIMEOUT_MS` | `60000` | Reserved compatibility setting |
515
+ | `AI_ALLOW_DESIGN_MUTATIONS` | `false` | Reserved; cannot enable AI-originated design mutation |
504
516
 
505
- | Variable | Default | Description |
506
- | --------------------------- | ------- | ----------------------------------------------- |
507
- | `AI_PROVIDER` | `none` | `anthropic`, `openai`, `openrouter`, or `local` |
508
- | `AI_MODEL` | `''` | Model name (e.g., `claude-sonnet-4-20250514`) |
509
- | `AI_API_KEY` | `''` | AI provider API key |
510
- | `AI_MAX_TOKENS` | `8000` | Max tokens per AI response |
511
- | `AI_TIMEOUT_MS` | `60000` | AI request timeout in ms |
512
- | `AI_ALLOW_DESIGN_MUTATIONS` | `false` | Allow AI to modify schematic/board designs |
517
+ Use `easyeda_get_feature_flags` or `easyeda_get_capabilities` to inspect `configured`, `effective`,
518
+ and `maturity` values for optional settings.
513
519
 
514
520
  ### HTTP transport
515
521
 
@@ -570,12 +576,19 @@ See `.env.example` for the complete list of configuration variables.
570
576
 
571
577
  ## MCP Tools
572
578
 
573
- The server currently registers up to 77 profile-gated tools. Tools are filtered by the active `TOOL_PROFILE`:
579
+ The server registers profile-gated tools according to the active `TOOL_PROFILE`. The table below is generated from the same registry used at runtime:
580
+
581
+ <!-- capability-counts:start -->
574
582
 
575
- - `core`: 48 tools
576
- - `pro`: 63 tools
577
- - `full`: 73 tools
578
- - `dev`: 77 tools
583
+ | Profile | Registered tools |
584
+ | -------------- | ---------------: |
585
+ | `core` | 71 |
586
+ | `pro` | 98 |
587
+ | `full` | 110 |
588
+ | `dev` | 115 |
589
+ | `experimental` | 115 |
590
+
591
+ <!-- capability-counts:end -->
579
592
 
580
593
  `core` exposes the standard workflow tools, `pro` adds manufacturing exports, `full` adds controlled documented EasyEDA API calls, and `dev` adds runtime probes for debugging.
581
594
 
@@ -677,7 +690,7 @@ The schematic write APIs use EasyEDA Pro extension APIs that EasyEDA currently m
677
690
  │ (via Plugin) │ Protocol │ └───────────────┘ │
678
691
  └─────────────────┘ │ ┌───────────────┐ │
679
692
  │ │ ToolRegistry │ │
680
- │ │ (up to 60 tools) │ │
693
+ │ │ (up to 115 tools) │ │
681
694
  │ └───────────────┘ │
682
695
  │ ┌───────────────┐ │
683
696
  │ │ Storage │──┼──► SQLite
@@ -712,6 +725,8 @@ required by EasyEDA Pro.
712
725
 
713
726
  Installation: Open EasyEDA Pro → **Settings** → **Extensions** → **Extension Manager...** → **Import Extension**, then select the `.eext` file. Make sure **Allow External Interaction** is enabled for the extension.
714
727
 
728
+ For local bridge development, an experimental loopback-only CDP transport is documented in the [CDP Bridge guide](docs/guide/cdp-bridge.md). The extension remains the recommended transport for normal use. Public delivery targets and milestone lifecycle rules are maintained in the [roadmap](docs/ROADMAP.md).
729
+
715
730
  ---
716
731
 
717
732
  ## Agent plugin and skills
@@ -815,7 +830,7 @@ pnpm typecheck # TypeScript
815
830
  pnpm lint # ESLint
816
831
 
817
832
  # Test
818
- pnpm test # Vitest (812 tests across 67 files)
833
+ pnpm test # Vitest suite
819
834
  pnpm test:coverage # With coverage report
820
835
 
821
836
  # Golden E2E fixture smoke tests are included in `pnpm test`
@@ -868,7 +883,7 @@ src/
868
883
  │ ├── http.ts # HTTP/Streamable HTTP transport
869
884
  │ └── oauth-resource-metadata.ts
870
885
  ├── storage/ # Node.js sqlite storage (cache, artifacts)
871
- ├── tools/ # Up to 60 profile-gated MCP tool definitions
886
+ ├── tools/ # Up to 115 profile-gated MCP tool definitions
872
887
  │ ├── register.ts, registry.ts, types.ts, transaction.ts
873
888
  │ ├── L0_diagnostics_core.ts, L0_diagnostics_api.ts
874
889
  │ ├── L1_schematic_read.ts, L1_schematic_write.ts
@@ -906,8 +921,8 @@ See [Security Architecture & Threat Model](docs/security-architecture.md) for th
906
921
  This repository uses automated workflows to manage dependencies and releases:
907
922
 
908
923
  - **Renovate**: Automatically scans and updates dependencies based on rules configured in [.github/renovate.json](.github/renovate.json). For details on PR policies and automerging, see [Repository Governance](docs/REPOSITORY_GOVERNANCE.md).
909
- - **Release Please**: Automatically bumps package versions, updates files (like `package.json`, `server.json`, `extension.json`), and generates `CHANGELOG.md` upon merging Release PRs. For the full release procedure and Conventional Commit conventions, see [Release Process](docs/RELEASE_PROCESS.md).
910
- - **Secure Publishing**: The release workflow builds all assets (including `easyeda-bridge-extension.eext`), publishes to the NPM registry with cryptographic provenance, and uploads assets directly to the GitHub release.
924
+ - **Release Please**: Automates stable version bumps, release metadata, and `CHANGELOG.md`. Numbered `rc.N` candidates use the isolated prerelease path. See the [Release Policy](docs/RELEASE_POLICY.md) and [Release Process](docs/RELEASE_PROCESS.md).
925
+ - **Secure Publishing**: The release workflow rebuilds and verifies all assets, publishes npm with provenance to channel-safe `latest` or `next` dist-tags, uploads the extension and SBOM to the matching GitHub Release, and keeps GHCR/MCP Registry promotion aligned with the selected channel.
911
926
 
912
927
  ---
913
928
 
@@ -1,15 +1,17 @@
1
+ import { getFeatureMaturity } from './feature-maturity.js';
1
2
  export function loadFeatureFlags(config) {
3
+ const maturity = getFeatureMaturity(config);
2
4
  return {
3
- mcpTasksEnabled: config.MCP_TASKS_ENABLED,
4
- mcpAppsEnabled: config.MCP_APPS_ENABLED,
5
- mcpV2Experimental: config.MCP_V2_EXPERIMENTAL,
5
+ mcpTasksEnabled: maturity.mcp_tasks.effective,
6
+ mcpAppsEnabled: maturity.mcp_apps.effective,
7
+ mcpV2Experimental: maturity.mcp_v2.effective,
6
8
  jlcpcbOrderingEnabled: config.JLCPCB_ENABLE_ORDERING,
7
9
  jlcsearchEnabled: config.JLCSEARCH_ENABLED,
8
10
  mouserEnabled: config.MOUSER_ENABLED,
9
11
  digikeyEnabled: config.DIGIKEY_ENABLED,
10
- oauthEnabled: config.OAUTH_ENABLED,
11
- otelEnabled: config.OTEL_ENABLED,
12
- aiEnabled: config.AI_PROVIDER !== 'none',
12
+ oauthEnabled: maturity.oauth.effective,
13
+ otelEnabled: maturity.otel_export.effective,
14
+ aiEnabled: maturity.ai_provider.effective,
13
15
  devBridge: config.EASYEDA_DEV_BRIDGE,
14
16
  bridgeRawExecEnabled: config.BRIDGE_RAW_EXEC_ENABLED,
15
17
  rawExecExperimental: config.MCP_RAW_EXEC_EXPERIMENTAL,
@@ -1 +1 @@
1
- {"version":3,"file":"feature-flags.js","sourceRoot":"","sources":["../../src/config/feature-flags.ts"],"names":[],"mappings":"AAkBA,MAAM,UAAU,gBAAgB,CAAC,MAAiB;IAChD,OAAO;QACL,eAAe,EAAE,MAAM,CAAC,iBAAiB;QACzC,cAAc,EAAE,MAAM,CAAC,gBAAgB;QACvC,iBAAiB,EAAE,MAAM,CAAC,mBAAmB;QAC7C,qBAAqB,EAAE,MAAM,CAAC,sBAAsB;QACpD,gBAAgB,EAAE,MAAM,CAAC,iBAAiB;QAC1C,aAAa,EAAE,MAAM,CAAC,cAAc;QACpC,cAAc,EAAE,MAAM,CAAC,eAAe;QACtC,YAAY,EAAE,MAAM,CAAC,aAAa;QAClC,WAAW,EAAE,MAAM,CAAC,YAAY;QAChC,SAAS,EAAE,MAAM,CAAC,WAAW,KAAK,MAAM;QACxC,SAAS,EAAE,MAAM,CAAC,kBAAkB;QACpC,oBAAoB,EAAE,MAAM,CAAC,uBAAuB;QACpD,mBAAmB,EAAE,MAAM,CAAC,yBAAyB;KACtD,CAAC;AACJ,CAAC"}
1
+ {"version":3,"file":"feature-flags.js","sourceRoot":"","sources":["../../src/config/feature-flags.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,kBAAkB,EAAE,MAAM,uBAAuB,CAAC;AAkB3D,MAAM,UAAU,gBAAgB,CAAC,MAAiB;IAChD,MAAM,QAAQ,GAAG,kBAAkB,CAAC,MAAM,CAAC,CAAC;IAC5C,OAAO;QACL,eAAe,EAAE,QAAQ,CAAC,SAAS,CAAC,SAAS;QAC7C,cAAc,EAAE,QAAQ,CAAC,QAAQ,CAAC,SAAS;QAC3C,iBAAiB,EAAE,QAAQ,CAAC,MAAM,CAAC,SAAS;QAC5C,qBAAqB,EAAE,MAAM,CAAC,sBAAsB;QACpD,gBAAgB,EAAE,MAAM,CAAC,iBAAiB;QAC1C,aAAa,EAAE,MAAM,CAAC,cAAc;QACpC,cAAc,EAAE,MAAM,CAAC,eAAe;QACtC,YAAY,EAAE,QAAQ,CAAC,KAAK,CAAC,SAAS;QACtC,WAAW,EAAE,QAAQ,CAAC,WAAW,CAAC,SAAS;QAC3C,SAAS,EAAE,QAAQ,CAAC,WAAW,CAAC,SAAS;QACzC,SAAS,EAAE,MAAM,CAAC,kBAAkB;QACpC,oBAAoB,EAAE,MAAM,CAAC,uBAAuB;QACpD,mBAAmB,EAAE,MAAM,CAAC,yBAAyB;KACtD,CAAC;AACJ,CAAC"}
@@ -0,0 +1,25 @@
1
+ import { type EnvConfig } from './env.js';
2
+ export type FeatureMaturity = 'implemented' | 'experimental' | 'reserved';
3
+ export interface FeatureMaturityEntry {
4
+ maturity: FeatureMaturity;
5
+ configured: boolean;
6
+ effective: boolean;
7
+ note: string;
8
+ }
9
+ export interface FeatureMaturityReport {
10
+ mcp_tasks: FeatureMaturityEntry;
11
+ mcp_apps: FeatureMaturityEntry;
12
+ mcp_v2: FeatureMaturityEntry;
13
+ ai_provider: FeatureMaturityEntry;
14
+ otel_export: FeatureMaturityEntry;
15
+ remote_relay: FeatureMaturityEntry;
16
+ raw_execution: FeatureMaturityEntry;
17
+ oauth: FeatureMaturityEntry;
18
+ [key: string]: FeatureMaturityEntry;
19
+ }
20
+ /**
21
+ * Describe user-visible configuration independently from whether a value was set.
22
+ * Reserved settings remain parseable for compatibility but cannot be reported as
23
+ * active until a runtime implementation consumes them.
24
+ */
25
+ export declare function getFeatureMaturity(config: EnvConfig): FeatureMaturityReport;
@@ -0,0 +1,60 @@
1
+ /**
2
+ * Describe user-visible configuration independently from whether a value was set.
3
+ * Reserved settings remain parseable for compatibility but cannot be reported as
4
+ * active until a runtime implementation consumes them.
5
+ */
6
+ export function getFeatureMaturity(config) {
7
+ return {
8
+ mcp_tasks: {
9
+ maturity: 'reserved',
10
+ configured: config.MCP_TASKS_ENABLED,
11
+ effective: false,
12
+ note: 'The setting is reserved; this server does not advertise or execute MCP Tasks.',
13
+ },
14
+ mcp_apps: {
15
+ maturity: 'reserved',
16
+ configured: config.MCP_APPS_ENABLED,
17
+ effective: false,
18
+ note: 'The setting is reserved; no MCP Apps UI/resource runtime is registered.',
19
+ },
20
+ mcp_v2: {
21
+ maturity: 'reserved',
22
+ configured: config.MCP_V2_EXPERIMENTAL,
23
+ effective: false,
24
+ note: 'The setting is reserved and does not change the negotiated MCP protocol.',
25
+ },
26
+ ai_provider: {
27
+ maturity: 'reserved',
28
+ configured: config.AI_PROVIDER !== 'none',
29
+ effective: false,
30
+ note: 'Provider/model settings are reserved; no in-process AI provider is invoked.',
31
+ },
32
+ otel_export: {
33
+ maturity: 'reserved',
34
+ configured: config.OTEL_ENABLED,
35
+ effective: false,
36
+ note: 'OTLP settings are reserved; the current observability path is local structured metrics.',
37
+ },
38
+ remote_relay: {
39
+ maturity: 'experimental',
40
+ configured: config.MCP_BRIDGE_BACKEND === 'remote_relay',
41
+ effective: config.MCP_BRIDGE_BACKEND === 'remote_relay',
42
+ note: 'The paired relay path is implemented behind explicit configuration and is not Beta.',
43
+ },
44
+ raw_execution: {
45
+ maturity: 'experimental',
46
+ configured: config.MCP_RAW_EXEC_EXPERIMENTAL || config.BRIDGE_RAW_EXEC_ENABLED,
47
+ effective: config.NODE_ENV !== 'production' &&
48
+ config.MCP_RAW_EXEC_EXPERIMENTAL &&
49
+ config.BRIDGE_RAW_EXEC_ENABLED,
50
+ note: 'Raw execution requires both development-only gates and remains quarantined.',
51
+ },
52
+ oauth: {
53
+ maturity: 'implemented',
54
+ configured: config.OAUTH_ENABLED,
55
+ effective: config.OAUTH_ENABLED,
56
+ note: 'OAuth/JWKS validation is implemented and mandatory for non-loopback HTTP.',
57
+ },
58
+ };
59
+ }
60
+ //# sourceMappingURL=feature-maturity.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"feature-maturity.js","sourceRoot":"","sources":["../../src/config/feature-maturity.ts"],"names":[],"mappings":"AAuBA;;;;GAIG;AACH,MAAM,UAAU,kBAAkB,CAAC,MAAiB;IAClD,OAAO;QACL,SAAS,EAAE;YACT,QAAQ,EAAE,UAAU;YACpB,UAAU,EAAE,MAAM,CAAC,iBAAiB;YACpC,SAAS,EAAE,KAAK;YAChB,IAAI,EAAE,+EAA+E;SACtF;QACD,QAAQ,EAAE;YACR,QAAQ,EAAE,UAAU;YACpB,UAAU,EAAE,MAAM,CAAC,gBAAgB;YACnC,SAAS,EAAE,KAAK;YAChB,IAAI,EAAE,yEAAyE;SAChF;QACD,MAAM,EAAE;YACN,QAAQ,EAAE,UAAU;YACpB,UAAU,EAAE,MAAM,CAAC,mBAAmB;YACtC,SAAS,EAAE,KAAK;YAChB,IAAI,EAAE,0EAA0E;SACjF;QACD,WAAW,EAAE;YACX,QAAQ,EAAE,UAAU;YACpB,UAAU,EAAE,MAAM,CAAC,WAAW,KAAK,MAAM;YACzC,SAAS,EAAE,KAAK;YAChB,IAAI,EAAE,6EAA6E;SACpF;QACD,WAAW,EAAE;YACX,QAAQ,EAAE,UAAU;YACpB,UAAU,EAAE,MAAM,CAAC,YAAY;YAC/B,SAAS,EAAE,KAAK;YAChB,IAAI,EAAE,yFAAyF;SAChG;QACD,YAAY,EAAE;YACZ,QAAQ,EAAE,cAAc;YACxB,UAAU,EAAE,MAAM,CAAC,kBAAkB,KAAK,cAAc;YACxD,SAAS,EAAE,MAAM,CAAC,kBAAkB,KAAK,cAAc;YACvD,IAAI,EAAE,qFAAqF;SAC5F;QACD,aAAa,EAAE;YACb,QAAQ,EAAE,cAAc;YACxB,UAAU,EAAE,MAAM,CAAC,yBAAyB,IAAI,MAAM,CAAC,uBAAuB;YAC9E,SAAS,EACP,MAAM,CAAC,QAAQ,KAAK,YAAY;gBAChC,MAAM,CAAC,yBAAyB;gBAChC,MAAM,CAAC,uBAAuB;YAChC,IAAI,EAAE,6EAA6E;SACpF;QACD,KAAK,EAAE;YACL,QAAQ,EAAE,aAAa;YACvB,UAAU,EAAE,MAAM,CAAC,aAAa;YAChC,SAAS,EAAE,MAAM,CAAC,aAAa;YAC/B,IAAI,EAAE,2EAA2E;SAClF;KACF,CAAC;AACJ,CAAC"}
@@ -1 +1 @@
1
- export declare const SERVER_VERSION = "0.35.2";
1
+ export declare const SERVER_VERSION = "0.35.4";
@@ -1,2 +1,2 @@
1
- export const SERVER_VERSION = '0.35.2'; // x-release-please-version
1
+ export const SERVER_VERSION = '0.35.4'; // x-release-please-version
2
2
  //# sourceMappingURL=version.js.map
@@ -0,0 +1,96 @@
1
+ export declare function sortUnknownForStableHash(value: unknown): unknown;
2
+ export declare function extractPrimitiveId(value: unknown): string | undefined;
3
+ export declare function primitiveDescriptorHash(snapshot: unknown): string;
4
+ export declare function extractPrimitiveIds(value: unknown): string[];
5
+ export interface SchematicTransactionFixtureIdentity {
6
+ project: string;
7
+ schematic: string;
8
+ page: string;
9
+ disposable: true;
10
+ }
11
+ export interface SchematicTransactionRuntimeIdentity {
12
+ projectInfo: unknown;
13
+ schematicInfo: unknown;
14
+ pageInfo: unknown;
15
+ }
16
+ export interface SchematicTransactionFixtureMismatch {
17
+ field: 'project' | 'schematic' | 'page';
18
+ expected: string;
19
+ actual?: string;
20
+ }
21
+ export declare function verifySchematicTransactionFixtureIdentity(expected: SchematicTransactionFixtureIdentity, runtime: SchematicTransactionRuntimeIdentity): {
22
+ ok: boolean;
23
+ actual: {
24
+ project?: string;
25
+ schematic?: string;
26
+ page?: string;
27
+ };
28
+ mismatches: SchematicTransactionFixtureMismatch[];
29
+ };
30
+ export interface SchematicTransactionStateDigest {
31
+ primitiveInventoryHash: string;
32
+ componentHash: string;
33
+ netHash: string;
34
+ ercAvailable: boolean;
35
+ ercHash?: string;
36
+ }
37
+ export interface SchematicTransactionOutcome {
38
+ passed: boolean;
39
+ rolledBack: boolean;
40
+ stateRestored: boolean;
41
+ details?: Record<string, unknown>;
42
+ }
43
+ export interface SchematicTransactionSmokeInput {
44
+ generatedAt?: string;
45
+ environment: {
46
+ operatingSystem: string;
47
+ architecture: string;
48
+ nodeVersion: string;
49
+ };
50
+ fixture: SchematicTransactionFixtureIdentity;
51
+ bridge: {
52
+ easyedaVersion?: string;
53
+ bridgeVersion?: string;
54
+ dispatcherBuildId?: string;
55
+ methodRegistryHash?: string;
56
+ activePort?: number;
57
+ };
58
+ outcomes: {
59
+ createRollback: SchematicTransactionOutcome;
60
+ modifyRollback: SchematicTransactionOutcome;
61
+ deleteRollback: SchematicTransactionOutcome;
62
+ };
63
+ cleanup: {
64
+ remainingIds: string[];
65
+ errors: string[];
66
+ };
67
+ baseline: SchematicTransactionStateDigest;
68
+ finalState: SchematicTransactionStateDigest;
69
+ error?: string;
70
+ }
71
+ export type SchematicTransactionSmokeCheckId = 'create-rollback' | 'modify-rollback' | 'delete-rollback' | 'final-state-restored';
72
+ export interface SchematicTransactionSmokeCheck {
73
+ id: SchematicTransactionSmokeCheckId;
74
+ status: 'passed' | 'failed';
75
+ details: Record<string, unknown>;
76
+ }
77
+ export interface SchematicTransactionSmokeReport {
78
+ schemaVersion: 1;
79
+ status: 'passed' | 'failed';
80
+ generatedAt: string;
81
+ environment: SchematicTransactionSmokeInput['environment'];
82
+ fixture: SchematicTransactionFixtureIdentity;
83
+ bridge: SchematicTransactionSmokeInput['bridge'];
84
+ checks: SchematicTransactionSmokeCheck[];
85
+ cleanup: {
86
+ clean: boolean;
87
+ remainingIds: string[];
88
+ errors: string[];
89
+ };
90
+ baseline: SchematicTransactionStateDigest;
91
+ finalState: SchematicTransactionStateDigest;
92
+ error?: string;
93
+ }
94
+ export declare function compareSchematicTransactionState(baseline: SchematicTransactionStateDigest, finalState: SchematicTransactionStateDigest): Record<string, boolean>;
95
+ export declare function buildSchematicTransactionSmokeReport(input: SchematicTransactionSmokeInput): SchematicTransactionSmokeReport;
96
+ export declare function writeSchematicTransactionSmokeReport(path: string, report: SchematicTransactionSmokeReport): Promise<void>;
@@ -0,0 +1,160 @@
1
+ import { mkdir, rename, writeFile } from 'node:fs/promises';
2
+ import { dirname, resolve } from 'node:path';
3
+ import { stableHash } from '../transactions/stable.js';
4
+ export function sortUnknownForStableHash(value) {
5
+ if (Array.isArray(value)) {
6
+ return value
7
+ .map(sortUnknownForStableHash)
8
+ .sort((left, right) => JSON.stringify(left).localeCompare(JSON.stringify(right)));
9
+ }
10
+ if (value && typeof value === 'object') {
11
+ return Object.fromEntries(Object.entries(value)
12
+ .sort(([left], [right]) => left.localeCompare(right))
13
+ .map(([key, nested]) => [key, sortUnknownForStableHash(nested)]));
14
+ }
15
+ return value;
16
+ }
17
+ const PRIMITIVE_ID_KEYS = ['primitiveId', 'primitiveUuid', 'id', 'uuid'];
18
+ const PRIMITIVE_NESTED_KEYS = ['result', 'data', 'text', 'rectangle'];
19
+ export function extractPrimitiveId(value) {
20
+ if (typeof value === 'string')
21
+ return value.trim() || undefined;
22
+ if (Array.isArray(value)) {
23
+ for (const item of value) {
24
+ const id = extractPrimitiveId(item);
25
+ if (id)
26
+ return id;
27
+ }
28
+ return undefined;
29
+ }
30
+ if (!value || typeof value !== 'object')
31
+ return undefined;
32
+ const record = value;
33
+ for (const key of PRIMITIVE_ID_KEYS) {
34
+ const id = extractPrimitiveId(record[key]);
35
+ if (id)
36
+ return id;
37
+ }
38
+ for (const key of PRIMITIVE_NESTED_KEYS) {
39
+ const id = extractPrimitiveId(record[key]);
40
+ if (id)
41
+ return id;
42
+ }
43
+ return undefined;
44
+ }
45
+ export function primitiveDescriptorHash(snapshot) {
46
+ if (!snapshot || typeof snapshot !== 'object' || Array.isArray(snapshot)) {
47
+ return stableHash(snapshot);
48
+ }
49
+ const copy = structuredClone(snapshot);
50
+ delete copy.primitiveId;
51
+ return stableHash(copy);
52
+ }
53
+ export function extractPrimitiveIds(value) {
54
+ if (!value || typeof value !== 'object' || Array.isArray(value))
55
+ return [];
56
+ const primitiveIds = value.primitiveIds;
57
+ return Array.isArray(primitiveIds)
58
+ ? primitiveIds
59
+ .filter((id) => typeof id === 'string' && id.length > 0)
60
+ .sort((a, b) => a.localeCompare(b))
61
+ : [];
62
+ }
63
+ function recordString(value, keys) {
64
+ if (!value || typeof value !== 'object' || Array.isArray(value))
65
+ return undefined;
66
+ const record = value;
67
+ for (const key of keys) {
68
+ const candidate = record[key];
69
+ if (typeof candidate === 'string' && candidate.trim())
70
+ return candidate.trim();
71
+ }
72
+ return undefined;
73
+ }
74
+ export function verifySchematicTransactionFixtureIdentity(expected, runtime) {
75
+ const actual = {
76
+ project: recordString(runtime.projectInfo, ['friendlyName', 'name']),
77
+ schematic: recordString(runtime.schematicInfo, ['name']),
78
+ page: recordString(runtime.pageInfo, ['name']),
79
+ };
80
+ const mismatches = [];
81
+ for (const field of ['project', 'schematic', 'page']) {
82
+ if (actual[field] !== expected[field]) {
83
+ mismatches.push({
84
+ field,
85
+ expected: expected[field],
86
+ ...(actual[field] ? { actual: actual[field] } : {}),
87
+ });
88
+ }
89
+ }
90
+ return { ok: mismatches.length === 0, actual, mismatches };
91
+ }
92
+ function outcomeCheck(id, outcome) {
93
+ const passed = outcome.passed && outcome.rolledBack && outcome.stateRestored;
94
+ return {
95
+ id,
96
+ status: passed ? 'passed' : 'failed',
97
+ details: {
98
+ passed: outcome.passed,
99
+ rolledBack: outcome.rolledBack,
100
+ stateRestored: outcome.stateRestored,
101
+ ...(outcome.details ?? {}),
102
+ },
103
+ };
104
+ }
105
+ export function compareSchematicTransactionState(baseline, finalState) {
106
+ const ercComparable = baseline.ercAvailable && finalState.ercAvailable;
107
+ return {
108
+ primitiveInventoryHashEqual: baseline.primitiveInventoryHash === finalState.primitiveInventoryHash,
109
+ componentHashEqual: baseline.componentHash === finalState.componentHash,
110
+ netHashEqual: baseline.netHash === finalState.netHash,
111
+ ercComparable,
112
+ ercStateEqual: !ercComparable || baseline.ercHash === finalState.ercHash,
113
+ };
114
+ }
115
+ export function buildSchematicTransactionSmokeReport(input) {
116
+ const finalComparison = compareSchematicTransactionState(input.baseline, input.finalState);
117
+ const finalStatePassed = finalComparison.primitiveInventoryHashEqual &&
118
+ finalComparison.componentHashEqual &&
119
+ finalComparison.netHashEqual &&
120
+ finalComparison.ercStateEqual;
121
+ const checks = [
122
+ outcomeCheck('create-rollback', input.outcomes.createRollback),
123
+ outcomeCheck('modify-rollback', input.outcomes.modifyRollback),
124
+ outcomeCheck('delete-rollback', input.outcomes.deleteRollback),
125
+ {
126
+ id: 'final-state-restored',
127
+ status: finalStatePassed ? 'passed' : 'failed',
128
+ details: finalComparison,
129
+ },
130
+ ];
131
+ const cleanup = {
132
+ clean: input.cleanup.remainingIds.length === 0 && input.cleanup.errors.length === 0,
133
+ remainingIds: [...input.cleanup.remainingIds],
134
+ errors: [...input.cleanup.errors],
135
+ };
136
+ const status = checks.every((check) => check.status === 'passed') && cleanup.clean && !input.error
137
+ ? 'passed'
138
+ : 'failed';
139
+ return {
140
+ schemaVersion: 1,
141
+ status,
142
+ generatedAt: input.generatedAt ?? new Date().toISOString(),
143
+ environment: input.environment,
144
+ fixture: input.fixture,
145
+ bridge: input.bridge,
146
+ checks,
147
+ cleanup,
148
+ baseline: input.baseline,
149
+ finalState: input.finalState,
150
+ ...(input.error ? { error: input.error } : {}),
151
+ };
152
+ }
153
+ export async function writeSchematicTransactionSmokeReport(path, report) {
154
+ const absolutePath = resolve(path);
155
+ await mkdir(dirname(absolutePath), { recursive: true });
156
+ const temporaryPath = `${absolutePath}.${process.pid}.${Date.now()}.tmp`;
157
+ await writeFile(temporaryPath, `${JSON.stringify(report, null, 2)}\n`, 'utf8');
158
+ await rename(temporaryPath, absolutePath);
159
+ }
160
+ //# sourceMappingURL=schematic-transaction-smoke-report.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"schematic-transaction-smoke-report.js","sourceRoot":"","sources":["../../src/live/schematic-transaction-smoke-report.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,56 @@
1
+ export type ReleaseChannel = 'stable' | 'prerelease';
2
+ export type ProvenanceObservation = 'passed' | 'failed' | 'unverified';
3
+ export type ReleaseVerificationCheckStatus = 'passed' | 'failed';
4
+ export interface ReleaseVerificationExpectation {
5
+ repository: string;
6
+ packageName: string;
7
+ mcpName: string;
8
+ version: string;
9
+ tag: string;
10
+ channel: ReleaseChannel;
11
+ npmDistTag: string;
12
+ commitSha: string;
13
+ requiredAssets: string[];
14
+ requiredGhcrTags: string[];
15
+ }
16
+ export interface ReleaseAssetObservation {
17
+ name: string;
18
+ digest?: string;
19
+ }
20
+ export interface ReleaseVerificationObservation {
21
+ npm: {
22
+ version?: string;
23
+ distTags: Record<string, string | undefined>;
24
+ provenance: ProvenanceObservation;
25
+ workflowContextProof?: boolean;
26
+ };
27
+ github: {
28
+ tag?: string;
29
+ tagCommitSha?: string;
30
+ isDraft?: boolean;
31
+ isPrerelease?: boolean;
32
+ assets: ReleaseAssetObservation[];
33
+ };
34
+ ghcr: {
35
+ digest?: string;
36
+ tags: string[];
37
+ };
38
+ mcpRegistry: {
39
+ version?: string;
40
+ isLatest?: boolean;
41
+ } | null;
42
+ }
43
+ export type ReleaseVerificationCheckId = 'npm-version' | 'npm-dist-tag' | 'npm-provenance' | 'github-tag' | 'github-tag-commit' | 'github-classification' | 'github-assets' | 'ghcr-tags' | 'mcp-registry';
44
+ export interface ReleaseVerificationCheck {
45
+ id: ReleaseVerificationCheckId;
46
+ status: ReleaseVerificationCheckStatus;
47
+ expected: unknown;
48
+ actual: unknown;
49
+ message?: string;
50
+ }
51
+ export interface ReleaseVerificationReport {
52
+ ok: boolean;
53
+ checks: ReleaseVerificationCheck[];
54
+ failures: ReleaseVerificationCheck[];
55
+ }
56
+ export declare function verifyPublishedReleaseObservation(expectation: ReleaseVerificationExpectation, observation: ReleaseVerificationObservation): ReleaseVerificationReport;
@@ -0,0 +1,63 @@
1
+ function check(id, passed, expected, actual, message) {
2
+ return {
3
+ id,
4
+ status: passed ? 'passed' : 'failed',
5
+ expected,
6
+ actual,
7
+ ...(message ? { message } : {}),
8
+ };
9
+ }
10
+ function sortedUnique(values) {
11
+ return [...new Set(values)].sort((left, right) => left.localeCompare(right));
12
+ }
13
+ function sameStringSet(expected, actual) {
14
+ const normalizedExpected = sortedUnique(expected);
15
+ const normalizedActual = sortedUnique(actual);
16
+ return (normalizedExpected.length === normalizedActual.length &&
17
+ normalizedExpected.every((value, index) => value === normalizedActual[index]));
18
+ }
19
+ export function verifyPublishedReleaseObservation(expectation, observation) {
20
+ const expectedPrerelease = expectation.channel === 'prerelease';
21
+ const assetByName = new Map(observation.github.assets.map((asset) => [asset.name, asset]));
22
+ const missingOrUndigestedAssets = expectation.requiredAssets.filter((name) => {
23
+ const asset = assetByName.get(name);
24
+ return !asset || typeof asset.digest !== 'string' || asset.digest.trim().length === 0;
25
+ });
26
+ const provenancePassed = observation.npm.provenance === 'passed' || observation.npm.workflowContextProof === true;
27
+ const provenanceActual = {
28
+ registry: observation.npm.provenance,
29
+ workflowContextProof: observation.npm.workflowContextProof === true,
30
+ };
31
+ const ghcrDigestPresent = typeof observation.ghcr.digest === 'string' && observation.ghcr.digest.trim().length > 0;
32
+ const ghcrTagsMatch = sameStringSet(expectation.requiredGhcrTags, observation.ghcr.tags);
33
+ const mcpPassed = expectation.channel === 'stable'
34
+ ? observation.mcpRegistry?.version === expectation.version &&
35
+ observation.mcpRegistry?.isLatest === true
36
+ : observation.mcpRegistry === null;
37
+ const checks = [
38
+ check('npm-version', observation.npm.version === expectation.version, expectation.version, observation.npm.version, 'The exact npm package version must match the release version.'),
39
+ check('npm-dist-tag', observation.npm.distTags[expectation.npmDistTag] === expectation.version, { [expectation.npmDistTag]: expectation.version }, { [expectation.npmDistTag]: observation.npm.distTags[expectation.npmDistTag] }, 'The channel-specific npm dist-tag must point to the exact release version.'),
40
+ check('npm-provenance', provenancePassed, 'registry provenance passed or workflow-context proof present', provenanceActual, 'The npm package must have registry provenance or verified workflow-context proof.'),
41
+ check('github-tag', observation.github.tag === expectation.tag, expectation.tag, observation.github.tag, 'The GitHub Release tag must match the expected immutable release tag.'),
42
+ check('github-tag-commit', observation.github.tagCommitSha === expectation.commitSha, expectation.commitSha, observation.github.tagCommitSha, 'The release tag must resolve to the expected candidate commit.'),
43
+ check('github-classification', observation.github.isDraft === false &&
44
+ observation.github.isPrerelease === expectedPrerelease, { isDraft: false, isPrerelease: expectedPrerelease }, {
45
+ isDraft: observation.github.isDraft,
46
+ isPrerelease: observation.github.isPrerelease,
47
+ }, 'The GitHub Release draft/prerelease classification must match the channel.'),
48
+ check('github-assets', missingOrUndigestedAssets.length === 0, expectation.requiredAssets, observation.github.assets, missingOrUndigestedAssets.length > 0
49
+ ? `Missing or undigested assets: ${missingOrUndigestedAssets.join(', ')}`
50
+ : undefined),
51
+ check('ghcr-tags', ghcrDigestPresent && ghcrTagsMatch, { digest: 'non-empty', tags: sortedUnique(expectation.requiredGhcrTags) }, { digest: observation.ghcr.digest, tags: sortedUnique(observation.ghcr.tags) }, 'All exact and moving GHCR tags must resolve to one non-empty image digest.'),
52
+ check('mcp-registry', mcpPassed, expectation.channel === 'stable' ? { version: expectation.version, isLatest: true } : null, observation.mcpRegistry, expectation.channel === 'stable'
53
+ ? 'Stable releases must be present in the MCP Registry and marked latest.'
54
+ : 'Prereleases must not be published to the MCP Registry.'),
55
+ ];
56
+ const failures = checks.filter((item) => item.status === 'failed');
57
+ return {
58
+ ok: failures.length === 0,
59
+ checks,
60
+ failures,
61
+ };
62
+ }
63
+ //# sourceMappingURL=published-release-verifier.js.map