@objectstack/metadata 17.0.0-rc.0 → 17.0.0-rc.2

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/dist/index.d.cts CHANGED
@@ -1,8 +1,8 @@
1
1
  import * as System from '@objectstack/spec/system';
2
2
  import { MetadataFormat, MetadataLoaderContract, MetadataLoadOptions, MetadataLoadResult, MetadataStats, MetadataSaveOptions, MetadataSaveResult, MetadataWatchEvent, MetadataManagerConfig, PackagePublishResult, MetadataHistoryQueryOptions, MetadataHistoryQueryResult, MetadataDiffResult, MetadataHistoryRecord, MetadataHistoryRetentionPolicy } from '@objectstack/spec/system';
3
- export { MetadataCollectionInfo, MetadataDiffResult, MetadataExportOptions, MetadataFormat, MetadataHistoryQueryOptions, MetadataHistoryQueryResult, MetadataHistoryRecord, MetadataHistoryRetentionPolicy, MetadataImportOptions, MetadataLoadOptions, MetadataLoadResult, MetadataLoaderContract, MetadataManagerConfig, MetadataSaveOptions, MetadataSaveResult, MetadataStats, MetadataWatchEvent } from '@objectstack/spec/system';
3
+ export { MetadataCollectionInfo, MetadataDiffResult, MetadataFormat, MetadataHistoryQueryOptions, MetadataHistoryQueryResult, MetadataHistoryRecord, MetadataHistoryRetentionPolicy, MetadataLoadOptions, MetadataLoadResult, MetadataLoaderContract, MetadataManagerConfig, MetadataSaveOptions, MetadataSaveResult, MetadataStats, MetadataWatchEvent } from '@objectstack/spec/system';
4
4
  import { IMetadataService, IDataDriver, IDataEngine, IRealtimeService, MetadataWriteOptions, MetadataWatchCallback, MetadataWatchHandle, MetadataExportOptions, MetadataImportOptions, MetadataImportResult, MetadataTypeInfo, IPubSub, ISchemaDriver } from '@objectstack/spec/contracts';
5
- export { IMetadataService, MetadataImportResult, MetadataTypeInfo, MetadataWatchCallback, MetadataWatchHandle } from '@objectstack/spec/contracts';
5
+ export { IMetadataService, MetadataExportOptions, MetadataImportOptions, MetadataImportResult, MetadataTypeInfo, MetadataWatchCallback, MetadataWatchHandle } from '@objectstack/spec/contracts';
6
6
  import { MetadataTypeRegistryEntry, MetadataQuery, MetadataQueryResult, MetadataBulkResult, MetadataOverlay, MetadataValidationResult, MetadataDependency, MetadataPluginConfig } from '@objectstack/spec/kernel';
7
7
  export { MetadataBulkResult, MetadataDependency, MetadataPluginConfig, MetadataPluginManifest, MetadataQuery, MetadataQueryResult, MetadataType, MetadataTypeRegistryEntry, MetadataValidationResult } from '@objectstack/spec/kernel';
8
8
  import { Logger, Plugin, PluginContext } from '@objectstack/core';
@@ -199,6 +199,28 @@ declare class MetadataManager implements IMetadataService {
199
199
  * @param service - An IRealtimeService instance for event publishing
200
200
  */
201
201
  setRealtimeService(service: IRealtimeService): void;
202
+ /**
203
+ * Publish a realtime {@link RealtimeMetadataEvent} for a metadata write
204
+ * (#4602 — contract-first).
205
+ *
206
+ * What reaches a `subscribeMetadata` callback must BE the spec's
207
+ * `MetadataEvent` (`@objectstack/spec/api`): `id` (uuid) at the top level,
208
+ * flattened `metadataType`/`name`/`definition`, `userId` when the write
209
+ * carried an actor. The transport keeps its `RealtimeEventPayload`
210
+ * envelope — `payload` carries the complete `MetadataEvent`, and the client
211
+ * SDK unwraps + validates it at the boundary.
212
+ *
213
+ * Two loud-by-design gates:
214
+ * - `MetadataEventType` is a CLOSED enum. A metadata type outside it has
215
+ * no declared realtime event contract, so we skip publishing (debug log)
216
+ * instead of emitting an event every compliant consumer must reject.
217
+ * Declared = enforced; widening coverage means widening the spec enum,
218
+ * not producing off-contract events.
219
+ * - The event body is `MetadataEventSchema.parse`d before publish, so a
220
+ * malformed producer fails here (warn log, event not published) rather
221
+ * than delivering a lie downstream.
222
+ */
223
+ private publishRealtimeMetadataEvent;
202
224
  /**
203
225
  * Register a new metadata loader (data source)
204
226
  */
@@ -444,8 +466,32 @@ declare class MetadataManager implements IMetadataService {
444
466
  /**
445
467
  * Load a single metadata item from loaders.
446
468
  * Iterates through registered loaders until found.
469
+ *
470
+ * Returns `null` both when no loader HAS the item and when every loader
471
+ * FAILED — see {@link loadDiagnosed} when the caller must tell those apart.
447
472
  */
448
473
  load<T = any>(type: string, name: string, options?: MetadataLoadOptions): Promise<T | null>;
474
+ /**
475
+ * `load`, plus whether the answer can be trusted as complete.
476
+ *
477
+ * [ADR-0110 D3] A miss and an outage are different facts with opposite
478
+ * security meanings, and plain `load` cannot express the difference: a
479
+ * loader that throws is warn-logged and skipped, so a database the metadata
480
+ * plane cannot reach returns the same `null` as a name that was never
481
+ * declared. Callers that gate on a declaration MUST NOT read that `null` as
482
+ * "the author declared no gate" — an availability failure would silently
483
+ * widen access (the REST `/actions` route's fail-open branch, #3935).
484
+ *
485
+ * `degraded` is true when at least one loader threw AND no loader answered
486
+ * with the item. The posture is deliberately conservative: with a loader
487
+ * down we cannot prove the item is absent, so we decline to claim it is.
488
+ * A clean miss (every loader answered, none had it) is NOT degraded.
489
+ */
490
+ loadDiagnosed<T = any>(type: string, name: string, options?: MetadataLoadOptions): Promise<{
491
+ data: T | null;
492
+ degraded: boolean;
493
+ errors: string[];
494
+ }>;
449
495
  /**
450
496
  * Load multiple metadata items from loaders.
451
497
  * Aggregates results from all loaders.
@@ -568,23 +614,38 @@ interface MetadataPluginOptions {
568
614
  config?: Partial<MetadataPluginConfig>;
569
615
  /** Organization ID for metadata-scoped consumers; MetadataPlugin itself does not persist runtime metadata. */
570
616
  organizationId?: string;
571
- /** Project ID used by local artifact envelopes and metadata-scoped consumers. */
617
+ /**
618
+ * Environment ID used by local artifact envelopes and metadata-scoped
619
+ * consumers. (The v5.0 rename retired the "project ID" wording this
620
+ * comment used to carry; see ADR-0006.)
621
+ */
572
622
  environmentId?: string;
573
623
  /**
574
- * When set, MetadataPlugin loads metadata from an artifact instead of scanning
575
- * the filesystem. Only `local-file` is implemented now; `artifact-api` is
576
- * reserved for M3/M4.
624
+ * When set, MetadataPlugin loads metadata from a compiled artifact instead
625
+ * of scanning the filesystem, honored by all three bootstrap modes
626
+ * (`eager` / `lazy` / `artifact-only`) — see `start()`.
627
+ *
628
+ * `path` is a filesystem path, or an `http(s)://` URL fetched verbatim —
629
+ * the control plane's public artifact route
630
+ * (`/pub/v1/environments/:id/artifact[?commit=…]`) serves exactly such
631
+ * URLs, so a sealed runtime can boot straight off a published revision.
632
+ * Remote reads honor `fetchTimeoutMs` / `OS_ARTIFACT_FETCH_TIMEOUT_MS`;
633
+ * the artifact-file HMR watcher ({@link artifactWatch}) applies to
634
+ * non-URL paths only.
635
+ *
636
+ * `local-file` is the only mode. A second `artifact-api` mode (a
637
+ * Bearer-authenticated control-plane pull) existed here through v17 with
638
+ * zero consumers in any repo — the cloud runtime uses its own
639
+ * `ArtifactApiClient`, and package distribution into a running OSS
640
+ * instance goes through `@objectstack/cloud-connection` — and was removed
641
+ * when #4246 forced the declared-vs-enforced question. `start()` rejects
642
+ * an unknown mode loudly rather than silently scanning the filesystem
643
+ * instead.
577
644
  */
578
645
  artifactSource?: {
579
646
  mode: 'local-file';
580
647
  path: string;
581
648
  fetchTimeoutMs?: number;
582
- } | {
583
- mode: 'artifact-api';
584
- url: string;
585
- token?: string;
586
- commitId?: string;
587
- fetchTimeoutMs?: number;
588
649
  };
589
650
  /**
590
651
  * Register the `sys_metadata` + `sys_metadata_history` storage objects
@@ -615,6 +676,20 @@ declare class MetadataPlugin implements Plugin {
615
676
  name: string;
616
677
  type: string;
617
678
  version: string;
679
+ /**
680
+ * Services init() UNCONDITIONALLY registers (ADR-0116, #4131) — lets the
681
+ * kernel name this plugin when a consumer requires `metadata` before it
682
+ * initializes.
683
+ */
684
+ providesServices: string[];
685
+ /**
686
+ * init() registers the metadata system objects through the `manifest`
687
+ * service ObjectQLPlugin provides — order-if-present so that
688
+ * registration is deterministic instead of "whichever init ran first"
689
+ * (ADR-0116, #4471). Soft, not hard: without an engine the plugin
690
+ * degrades on purpose (objects are discovered via the legacy fallback).
691
+ */
692
+ optionalDependencies: string[];
618
693
  private manager;
619
694
  private options;
620
695
  private repository?;
@@ -663,11 +738,19 @@ declare class MetadataPlugin implements Plugin {
663
738
  * logged but never blocks the reload.
664
739
  */
665
740
  private _reloadAndAnnounce;
666
- private _loadFromLocalFile;
667
741
  /**
668
- * P2: Load metadata from the cloud artifact API endpoint.
742
+ * @param opts.optional When true, a LOCAL artifact file that does not exist
743
+ * is "nothing compiled yet" rather than a fault: log and return, leaving
744
+ * the manager empty and the artifact watcher armed so the first
745
+ * `os compile` hydrates the running server (#4085). Callers pass it for
746
+ * the `eager` / `lazy` bootstrap modes — the development-platform paths,
747
+ * where an app is optional. `artifact-only` (sealed runtime) does NOT:
748
+ * there the artifact IS the deployment, so its absence must fail loudly
749
+ * instead of silently serving an empty runtime. Only ENOENT is tolerated;
750
+ * a present-but-unreadable artifact (malformed JSON, bad permissions) and
751
+ * every remote-URL failure stay fatal.
669
752
  */
670
- private _loadFromArtifactApi;
753
+ private _loadFromLocalFile;
671
754
  private _loadFromFileSystem;
672
755
  }
673
756
 
@@ -844,6 +927,19 @@ declare class DatabaseLoader implements MetadataLoader {
844
927
  private trackHistory;
845
928
  private schemaReady;
846
929
  private historySchemaReady;
930
+ /**
931
+ * Whether the loud "DDL failed" report has already been printed for the
932
+ * metadata table / history table respectively. AGENTS.md → "Degradation log
933
+ * levels": say it **once**, at the first degradation, not once per retry.
934
+ */
935
+ private schemaFailureReported;
936
+ private historySchemaFailureReported;
937
+ /**
938
+ * Same once-only discipline for the #4825 seam: the history table is readable
939
+ * or it is not, and repeating the report per skipped write turns a real
940
+ * degradation into noise people learn to skim.
941
+ */
942
+ private historySeqFailureReported;
847
943
  /** (type, name) → metadata payload — primes `load()` */
848
944
  private readonly loadCache?;
849
945
  /** type → array of payloads — primes `loadMany()` */
@@ -881,6 +977,24 @@ declare class DatabaseLoader implements MetadataLoader {
881
977
  * Reads `MAX(event_seq) + 1` for the configured `organization_id`.
882
978
  * Legacy path — not transactional, so concurrent writes can collide.
883
979
  * The canonical (transactional) producer is `SysMetadataRepository`.
980
+ *
981
+ * #4825 (same shape as #4728, rule from #4632) — discriminate by error TYPE.
982
+ * This used to `catch { return 1 }`, with a comment that named BOTH reasons a
983
+ * read can fail and then answered both the same way. Exactly one of them is
984
+ * benign: the history table has not been provisioned, so there is no row to
985
+ * be inconsistent with and 1 genuinely IS the next number. Every other reason
986
+ * — connection drop, timeout, insufficient privileges — means the rows are
987
+ * still there and simply were not seen, and answering 1 against a table with
988
+ * N rows **collides with existing rows**: the insert succeeds, the log stays
989
+ * empty, and `event_seq` (the ordering key that history listing and rollback
990
+ * targeting both stand on) is silently wrong from then on. Note this is the
991
+ * costlier half of the #4728 family — not bytes that never landed, but bytes
992
+ * that landed *wrong*, which no retry and no restart repairs.
993
+ *
994
+ * @throws The underlying driver error, unchanged, for every non-benign read
995
+ * failure. Deliberate: a sequence number this method cannot derive
996
+ * from data it actually read is not a number it may invent. The
997
+ * caller ({@link createHistoryRecord}) owns the consequence.
884
998
  */
885
999
  private nextEventSeq;
886
1000
  /**
@@ -915,8 +1029,23 @@ declare class DatabaseLoader implements MetadataLoader {
915
1029
  */
916
1030
  private createHistoryRecord;
917
1031
  /**
918
- * Convert a database row to a metadata payload.
919
- * Parses the JSON `metadata` column back into an object.
1032
+ * Once-per-process dedupe for stored-row conversion notices `load` /
1033
+ * `loadMany` are hot read paths (cached, but re-hit on every TTL expiry),
1034
+ * so a legacy row must warn once, not once per cache miss.
1035
+ */
1036
+ private storedConversionWarned;
1037
+ /**
1038
+ * Convert a LIVE database row to a metadata payload.
1039
+ *
1040
+ * Parses the JSON `metadata` column back into an object, then replays the
1041
+ * full ADR-0087 conversion chain over it (#3903): rows written under a past
1042
+ * protocol are served canonical, exactly like the metadata-protocol's
1043
+ * `sys_metadata` seams. History rows do NOT pass through here — history
1044
+ * readers parse inline and stay verbatim, as a record of what was written.
1045
+ *
1046
+ * `flow` is skipped for the same reason the protocol skips it: flow-node
1047
+ * conversions need the automation engine's live executor registry for their
1048
+ * open-namespace conflict guard; flows canonicalize at `registerFlow`.
920
1049
  */
921
1050
  private rowToData;
922
1051
  /**
package/dist/index.d.ts CHANGED
@@ -1,8 +1,8 @@
1
1
  import * as System from '@objectstack/spec/system';
2
2
  import { MetadataFormat, MetadataLoaderContract, MetadataLoadOptions, MetadataLoadResult, MetadataStats, MetadataSaveOptions, MetadataSaveResult, MetadataWatchEvent, MetadataManagerConfig, PackagePublishResult, MetadataHistoryQueryOptions, MetadataHistoryQueryResult, MetadataDiffResult, MetadataHistoryRecord, MetadataHistoryRetentionPolicy } from '@objectstack/spec/system';
3
- export { MetadataCollectionInfo, MetadataDiffResult, MetadataExportOptions, MetadataFormat, MetadataHistoryQueryOptions, MetadataHistoryQueryResult, MetadataHistoryRecord, MetadataHistoryRetentionPolicy, MetadataImportOptions, MetadataLoadOptions, MetadataLoadResult, MetadataLoaderContract, MetadataManagerConfig, MetadataSaveOptions, MetadataSaveResult, MetadataStats, MetadataWatchEvent } from '@objectstack/spec/system';
3
+ export { MetadataCollectionInfo, MetadataDiffResult, MetadataFormat, MetadataHistoryQueryOptions, MetadataHistoryQueryResult, MetadataHistoryRecord, MetadataHistoryRetentionPolicy, MetadataLoadOptions, MetadataLoadResult, MetadataLoaderContract, MetadataManagerConfig, MetadataSaveOptions, MetadataSaveResult, MetadataStats, MetadataWatchEvent } from '@objectstack/spec/system';
4
4
  import { IMetadataService, IDataDriver, IDataEngine, IRealtimeService, MetadataWriteOptions, MetadataWatchCallback, MetadataWatchHandle, MetadataExportOptions, MetadataImportOptions, MetadataImportResult, MetadataTypeInfo, IPubSub, ISchemaDriver } from '@objectstack/spec/contracts';
5
- export { IMetadataService, MetadataImportResult, MetadataTypeInfo, MetadataWatchCallback, MetadataWatchHandle } from '@objectstack/spec/contracts';
5
+ export { IMetadataService, MetadataExportOptions, MetadataImportOptions, MetadataImportResult, MetadataTypeInfo, MetadataWatchCallback, MetadataWatchHandle } from '@objectstack/spec/contracts';
6
6
  import { MetadataTypeRegistryEntry, MetadataQuery, MetadataQueryResult, MetadataBulkResult, MetadataOverlay, MetadataValidationResult, MetadataDependency, MetadataPluginConfig } from '@objectstack/spec/kernel';
7
7
  export { MetadataBulkResult, MetadataDependency, MetadataPluginConfig, MetadataPluginManifest, MetadataQuery, MetadataQueryResult, MetadataType, MetadataTypeRegistryEntry, MetadataValidationResult } from '@objectstack/spec/kernel';
8
8
  import { Logger, Plugin, PluginContext } from '@objectstack/core';
@@ -199,6 +199,28 @@ declare class MetadataManager implements IMetadataService {
199
199
  * @param service - An IRealtimeService instance for event publishing
200
200
  */
201
201
  setRealtimeService(service: IRealtimeService): void;
202
+ /**
203
+ * Publish a realtime {@link RealtimeMetadataEvent} for a metadata write
204
+ * (#4602 — contract-first).
205
+ *
206
+ * What reaches a `subscribeMetadata` callback must BE the spec's
207
+ * `MetadataEvent` (`@objectstack/spec/api`): `id` (uuid) at the top level,
208
+ * flattened `metadataType`/`name`/`definition`, `userId` when the write
209
+ * carried an actor. The transport keeps its `RealtimeEventPayload`
210
+ * envelope — `payload` carries the complete `MetadataEvent`, and the client
211
+ * SDK unwraps + validates it at the boundary.
212
+ *
213
+ * Two loud-by-design gates:
214
+ * - `MetadataEventType` is a CLOSED enum. A metadata type outside it has
215
+ * no declared realtime event contract, so we skip publishing (debug log)
216
+ * instead of emitting an event every compliant consumer must reject.
217
+ * Declared = enforced; widening coverage means widening the spec enum,
218
+ * not producing off-contract events.
219
+ * - The event body is `MetadataEventSchema.parse`d before publish, so a
220
+ * malformed producer fails here (warn log, event not published) rather
221
+ * than delivering a lie downstream.
222
+ */
223
+ private publishRealtimeMetadataEvent;
202
224
  /**
203
225
  * Register a new metadata loader (data source)
204
226
  */
@@ -444,8 +466,32 @@ declare class MetadataManager implements IMetadataService {
444
466
  /**
445
467
  * Load a single metadata item from loaders.
446
468
  * Iterates through registered loaders until found.
469
+ *
470
+ * Returns `null` both when no loader HAS the item and when every loader
471
+ * FAILED — see {@link loadDiagnosed} when the caller must tell those apart.
447
472
  */
448
473
  load<T = any>(type: string, name: string, options?: MetadataLoadOptions): Promise<T | null>;
474
+ /**
475
+ * `load`, plus whether the answer can be trusted as complete.
476
+ *
477
+ * [ADR-0110 D3] A miss and an outage are different facts with opposite
478
+ * security meanings, and plain `load` cannot express the difference: a
479
+ * loader that throws is warn-logged and skipped, so a database the metadata
480
+ * plane cannot reach returns the same `null` as a name that was never
481
+ * declared. Callers that gate on a declaration MUST NOT read that `null` as
482
+ * "the author declared no gate" — an availability failure would silently
483
+ * widen access (the REST `/actions` route's fail-open branch, #3935).
484
+ *
485
+ * `degraded` is true when at least one loader threw AND no loader answered
486
+ * with the item. The posture is deliberately conservative: with a loader
487
+ * down we cannot prove the item is absent, so we decline to claim it is.
488
+ * A clean miss (every loader answered, none had it) is NOT degraded.
489
+ */
490
+ loadDiagnosed<T = any>(type: string, name: string, options?: MetadataLoadOptions): Promise<{
491
+ data: T | null;
492
+ degraded: boolean;
493
+ errors: string[];
494
+ }>;
449
495
  /**
450
496
  * Load multiple metadata items from loaders.
451
497
  * Aggregates results from all loaders.
@@ -568,23 +614,38 @@ interface MetadataPluginOptions {
568
614
  config?: Partial<MetadataPluginConfig>;
569
615
  /** Organization ID for metadata-scoped consumers; MetadataPlugin itself does not persist runtime metadata. */
570
616
  organizationId?: string;
571
- /** Project ID used by local artifact envelopes and metadata-scoped consumers. */
617
+ /**
618
+ * Environment ID used by local artifact envelopes and metadata-scoped
619
+ * consumers. (The v5.0 rename retired the "project ID" wording this
620
+ * comment used to carry; see ADR-0006.)
621
+ */
572
622
  environmentId?: string;
573
623
  /**
574
- * When set, MetadataPlugin loads metadata from an artifact instead of scanning
575
- * the filesystem. Only `local-file` is implemented now; `artifact-api` is
576
- * reserved for M3/M4.
624
+ * When set, MetadataPlugin loads metadata from a compiled artifact instead
625
+ * of scanning the filesystem, honored by all three bootstrap modes
626
+ * (`eager` / `lazy` / `artifact-only`) — see `start()`.
627
+ *
628
+ * `path` is a filesystem path, or an `http(s)://` URL fetched verbatim —
629
+ * the control plane's public artifact route
630
+ * (`/pub/v1/environments/:id/artifact[?commit=…]`) serves exactly such
631
+ * URLs, so a sealed runtime can boot straight off a published revision.
632
+ * Remote reads honor `fetchTimeoutMs` / `OS_ARTIFACT_FETCH_TIMEOUT_MS`;
633
+ * the artifact-file HMR watcher ({@link artifactWatch}) applies to
634
+ * non-URL paths only.
635
+ *
636
+ * `local-file` is the only mode. A second `artifact-api` mode (a
637
+ * Bearer-authenticated control-plane pull) existed here through v17 with
638
+ * zero consumers in any repo — the cloud runtime uses its own
639
+ * `ArtifactApiClient`, and package distribution into a running OSS
640
+ * instance goes through `@objectstack/cloud-connection` — and was removed
641
+ * when #4246 forced the declared-vs-enforced question. `start()` rejects
642
+ * an unknown mode loudly rather than silently scanning the filesystem
643
+ * instead.
577
644
  */
578
645
  artifactSource?: {
579
646
  mode: 'local-file';
580
647
  path: string;
581
648
  fetchTimeoutMs?: number;
582
- } | {
583
- mode: 'artifact-api';
584
- url: string;
585
- token?: string;
586
- commitId?: string;
587
- fetchTimeoutMs?: number;
588
649
  };
589
650
  /**
590
651
  * Register the `sys_metadata` + `sys_metadata_history` storage objects
@@ -615,6 +676,20 @@ declare class MetadataPlugin implements Plugin {
615
676
  name: string;
616
677
  type: string;
617
678
  version: string;
679
+ /**
680
+ * Services init() UNCONDITIONALLY registers (ADR-0116, #4131) — lets the
681
+ * kernel name this plugin when a consumer requires `metadata` before it
682
+ * initializes.
683
+ */
684
+ providesServices: string[];
685
+ /**
686
+ * init() registers the metadata system objects through the `manifest`
687
+ * service ObjectQLPlugin provides — order-if-present so that
688
+ * registration is deterministic instead of "whichever init ran first"
689
+ * (ADR-0116, #4471). Soft, not hard: without an engine the plugin
690
+ * degrades on purpose (objects are discovered via the legacy fallback).
691
+ */
692
+ optionalDependencies: string[];
618
693
  private manager;
619
694
  private options;
620
695
  private repository?;
@@ -663,11 +738,19 @@ declare class MetadataPlugin implements Plugin {
663
738
  * logged but never blocks the reload.
664
739
  */
665
740
  private _reloadAndAnnounce;
666
- private _loadFromLocalFile;
667
741
  /**
668
- * P2: Load metadata from the cloud artifact API endpoint.
742
+ * @param opts.optional When true, a LOCAL artifact file that does not exist
743
+ * is "nothing compiled yet" rather than a fault: log and return, leaving
744
+ * the manager empty and the artifact watcher armed so the first
745
+ * `os compile` hydrates the running server (#4085). Callers pass it for
746
+ * the `eager` / `lazy` bootstrap modes — the development-platform paths,
747
+ * where an app is optional. `artifact-only` (sealed runtime) does NOT:
748
+ * there the artifact IS the deployment, so its absence must fail loudly
749
+ * instead of silently serving an empty runtime. Only ENOENT is tolerated;
750
+ * a present-but-unreadable artifact (malformed JSON, bad permissions) and
751
+ * every remote-URL failure stay fatal.
669
752
  */
670
- private _loadFromArtifactApi;
753
+ private _loadFromLocalFile;
671
754
  private _loadFromFileSystem;
672
755
  }
673
756
 
@@ -844,6 +927,19 @@ declare class DatabaseLoader implements MetadataLoader {
844
927
  private trackHistory;
845
928
  private schemaReady;
846
929
  private historySchemaReady;
930
+ /**
931
+ * Whether the loud "DDL failed" report has already been printed for the
932
+ * metadata table / history table respectively. AGENTS.md → "Degradation log
933
+ * levels": say it **once**, at the first degradation, not once per retry.
934
+ */
935
+ private schemaFailureReported;
936
+ private historySchemaFailureReported;
937
+ /**
938
+ * Same once-only discipline for the #4825 seam: the history table is readable
939
+ * or it is not, and repeating the report per skipped write turns a real
940
+ * degradation into noise people learn to skim.
941
+ */
942
+ private historySeqFailureReported;
847
943
  /** (type, name) → metadata payload — primes `load()` */
848
944
  private readonly loadCache?;
849
945
  /** type → array of payloads — primes `loadMany()` */
@@ -881,6 +977,24 @@ declare class DatabaseLoader implements MetadataLoader {
881
977
  * Reads `MAX(event_seq) + 1` for the configured `organization_id`.
882
978
  * Legacy path — not transactional, so concurrent writes can collide.
883
979
  * The canonical (transactional) producer is `SysMetadataRepository`.
980
+ *
981
+ * #4825 (same shape as #4728, rule from #4632) — discriminate by error TYPE.
982
+ * This used to `catch { return 1 }`, with a comment that named BOTH reasons a
983
+ * read can fail and then answered both the same way. Exactly one of them is
984
+ * benign: the history table has not been provisioned, so there is no row to
985
+ * be inconsistent with and 1 genuinely IS the next number. Every other reason
986
+ * — connection drop, timeout, insufficient privileges — means the rows are
987
+ * still there and simply were not seen, and answering 1 against a table with
988
+ * N rows **collides with existing rows**: the insert succeeds, the log stays
989
+ * empty, and `event_seq` (the ordering key that history listing and rollback
990
+ * targeting both stand on) is silently wrong from then on. Note this is the
991
+ * costlier half of the #4728 family — not bytes that never landed, but bytes
992
+ * that landed *wrong*, which no retry and no restart repairs.
993
+ *
994
+ * @throws The underlying driver error, unchanged, for every non-benign read
995
+ * failure. Deliberate: a sequence number this method cannot derive
996
+ * from data it actually read is not a number it may invent. The
997
+ * caller ({@link createHistoryRecord}) owns the consequence.
884
998
  */
885
999
  private nextEventSeq;
886
1000
  /**
@@ -915,8 +1029,23 @@ declare class DatabaseLoader implements MetadataLoader {
915
1029
  */
916
1030
  private createHistoryRecord;
917
1031
  /**
918
- * Convert a database row to a metadata payload.
919
- * Parses the JSON `metadata` column back into an object.
1032
+ * Once-per-process dedupe for stored-row conversion notices `load` /
1033
+ * `loadMany` are hot read paths (cached, but re-hit on every TTL expiry),
1034
+ * so a legacy row must warn once, not once per cache miss.
1035
+ */
1036
+ private storedConversionWarned;
1037
+ /**
1038
+ * Convert a LIVE database row to a metadata payload.
1039
+ *
1040
+ * Parses the JSON `metadata` column back into an object, then replays the
1041
+ * full ADR-0087 conversion chain over it (#3903): rows written under a past
1042
+ * protocol are served canonical, exactly like the metadata-protocol's
1043
+ * `sys_metadata` seams. History rows do NOT pass through here — history
1044
+ * readers parse inline and stay verbatim, as a record of what was written.
1045
+ *
1046
+ * `flow` is skipped for the same reason the protocol skips it: flow-node
1047
+ * conversions need the automation engine's live executor registry for their
1048
+ * open-namespace conflict guard; flows canonicalize at `registerFlow`.
920
1049
  */
921
1050
  private rowToData;
922
1051
  /**