@ontrails/core 1.0.0-beta.14 → 1.0.0-beta.16
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/CHANGELOG.md +74 -0
- package/README.md +73 -24
- package/package.json +11 -4
- package/src/activation-provenance.ts +111 -0
- package/src/activation-source-compatibility.ts +430 -0
- package/src/activation-source-projection.ts +221 -0
- package/src/activation-source.ts +91 -0
- package/src/blob-ref.ts +51 -0
- package/src/branded.ts +1 -1
- package/src/context.ts +28 -4
- package/src/contour.ts +344 -0
- package/src/cross-batch.ts +69 -0
- package/src/cross-schema.ts +36 -0
- package/src/detours.ts +8 -0
- package/src/draft.ts +60 -52
- package/src/error-projection.ts +51 -0
- package/src/errors.ts +245 -48
- package/src/execute.ts +1250 -40
- package/src/fire.ts +1169 -0
- package/src/index.ts +383 -49
- package/src/internal/fork-ctx.ts +69 -0
- package/src/layer-projection.ts +192 -0
- package/src/layer.ts +81 -0
- package/src/observe.ts +361 -0
- package/src/path-security.ts +1 -1
- package/src/permits.ts +12 -0
- package/src/redaction/patterns.ts +6 -3
- package/src/resilience.ts +11 -11
- package/src/resource-config.ts +792 -0
- package/src/resource.ts +175 -0
- package/src/result.ts +18 -0
- package/src/run.ts +7 -6
- package/src/schedule-runtime.ts +689 -0
- package/src/schedule.ts +324 -0
- package/src/serialization.ts +90 -55
- package/src/signal-diagnostics.ts +633 -0
- package/src/signal-ref.ts +87 -0
- package/src/signal.ts +37 -0
- package/src/store/accessor-protocol.ts +56 -0
- package/src/store/index.ts +4 -0
- package/src/structured-examples.ts +245 -0
- package/src/surface-derivation.ts +91 -0
- package/src/surface-filter.ts +176 -0
- package/src/topo.ts +710 -59
- package/src/tracing.ts +345 -0
- package/src/trail.ts +347 -33
- package/src/trails/derive-trail.ts +842 -0
- package/src/trails/index.ts +9 -0
- package/src/trails/ingest.ts +138 -0
- package/src/{internal/trails-db.ts → trails-db.ts} +50 -31
- package/src/transport-error-map.ts +174 -0
- package/src/type-utils.ts +17 -2
- package/src/types.ts +208 -17
- package/src/validate-established-topo.ts +8 -2
- package/src/validate-topo.ts +244 -13
- package/src/validation.ts +58 -1
- package/src/webhook.ts +276 -0
- package/src/workspace.ts +1 -1
- package/src/zod-wrappers.ts +72 -0
- package/.turbo/turbo-build.log +0 -1
- package/.turbo/turbo-lint.log +0 -3
- package/.turbo/turbo-typecheck.log +0 -1
- package/dist/adapters.d.ts +0 -39
- package/dist/adapters.d.ts.map +0 -1
- package/dist/adapters.js +0 -2
- package/dist/adapters.js.map +0 -1
- package/dist/blob-ref.d.ts +0 -20
- package/dist/blob-ref.d.ts.map +0 -1
- package/dist/blob-ref.js +0 -22
- package/dist/blob-ref.js.map +0 -1
- package/dist/branded.d.ts +0 -36
- package/dist/branded.d.ts.map +0 -1
- package/dist/branded.js +0 -89
- package/dist/branded.js.map +0 -1
- package/dist/collections.d.ts +0 -31
- package/dist/collections.d.ts.map +0 -1
- package/dist/collections.js +0 -60
- package/dist/collections.js.map +0 -1
- package/dist/context.d.ts +0 -10
- package/dist/context.d.ts.map +0 -1
- package/dist/context.js +0 -21
- package/dist/context.js.map +0 -1
- package/dist/derive.d.ts +0 -44
- package/dist/derive.d.ts.map +0 -1
- package/dist/derive.js +0 -149
- package/dist/derive.js.map +0 -1
- package/dist/dispatch.d.ts +0 -27
- package/dist/dispatch.d.ts.map +0 -1
- package/dist/dispatch.js +0 -34
- package/dist/dispatch.js.map +0 -1
- package/dist/draft.d.ts +0 -28
- package/dist/draft.d.ts.map +0 -1
- package/dist/draft.js +0 -156
- package/dist/draft.js.map +0 -1
- package/dist/errors.d.ts +0 -83
- package/dist/errors.d.ts.map +0 -1
- package/dist/errors.js +0 -142
- package/dist/errors.js.map +0 -1
- package/dist/event.d.ts +0 -8
- package/dist/event.d.ts.map +0 -1
- package/dist/event.js +0 -7
- package/dist/event.js.map +0 -1
- package/dist/execute.d.ts +0 -35
- package/dist/execute.d.ts.map +0 -1
- package/dist/execute.js +0 -90
- package/dist/execute.js.map +0 -1
- package/dist/fetch.d.ts +0 -15
- package/dist/fetch.d.ts.map +0 -1
- package/dist/fetch.js +0 -102
- package/dist/fetch.js.map +0 -1
- package/dist/gate.d.ts +0 -17
- package/dist/gate.d.ts.map +0 -1
- package/dist/gate.js +0 -21
- package/dist/gate.js.map +0 -1
- package/dist/guards.d.ts +0 -17
- package/dist/guards.d.ts.map +0 -1
- package/dist/guards.js +0 -25
- package/dist/guards.js.map +0 -1
- package/dist/health.d.ts +0 -18
- package/dist/health.d.ts.map +0 -1
- package/dist/health.js +0 -5
- package/dist/health.js.map +0 -1
- package/dist/index.d.ts +0 -48
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js +0 -51
- package/dist/index.js.map +0 -1
- package/dist/internal/topo-saves.d.ts +0 -47
- package/dist/internal/topo-saves.d.ts.map +0 -1
- package/dist/internal/topo-saves.js +0 -310
- package/dist/internal/topo-saves.js.map +0 -1
- package/dist/internal/topo-store-read.d.ts +0 -67
- package/dist/internal/topo-store-read.d.ts.map +0 -1
- package/dist/internal/topo-store-read.js +0 -222
- package/dist/internal/topo-store-read.js.map +0 -1
- package/dist/internal/topo-store.d.ts +0 -12
- package/dist/internal/topo-store.d.ts.map +0 -1
- package/dist/internal/topo-store.js +0 -571
- package/dist/internal/topo-store.js.map +0 -1
- package/dist/internal/trails-db.d.ts +0 -16
- package/dist/internal/trails-db.d.ts.map +0 -1
- package/dist/internal/trails-db.js +0 -118
- package/dist/internal/trails-db.js.map +0 -1
- package/dist/job.d.ts +0 -24
- package/dist/job.d.ts.map +0 -1
- package/dist/job.js +0 -17
- package/dist/job.js.map +0 -1
- package/dist/layer.d.ts +0 -6
- package/dist/layer.d.ts.map +0 -1
- package/dist/layer.js +0 -4
- package/dist/layer.js.map +0 -1
- package/dist/path-security.d.ts +0 -28
- package/dist/path-security.d.ts.map +0 -1
- package/dist/path-security.js +0 -63
- package/dist/path-security.js.map +0 -1
- package/dist/patterns/bulk.d.ts +0 -15
- package/dist/patterns/bulk.d.ts.map +0 -1
- package/dist/patterns/bulk.js +0 -14
- package/dist/patterns/bulk.js.map +0 -1
- package/dist/patterns/change.d.ts +0 -10
- package/dist/patterns/change.d.ts.map +0 -1
- package/dist/patterns/change.js +0 -10
- package/dist/patterns/change.js.map +0 -1
- package/dist/patterns/date-range.d.ts +0 -10
- package/dist/patterns/date-range.d.ts.map +0 -1
- package/dist/patterns/date-range.js +0 -10
- package/dist/patterns/date-range.js.map +0 -1
- package/dist/patterns/index.d.ts +0 -9
- package/dist/patterns/index.d.ts.map +0 -1
- package/dist/patterns/index.js +0 -9
- package/dist/patterns/index.js.map +0 -1
- package/dist/patterns/pagination.d.ts +0 -18
- package/dist/patterns/pagination.d.ts.map +0 -1
- package/dist/patterns/pagination.js +0 -18
- package/dist/patterns/pagination.js.map +0 -1
- package/dist/patterns/progress.d.ts +0 -11
- package/dist/patterns/progress.d.ts.map +0 -1
- package/dist/patterns/progress.js +0 -11
- package/dist/patterns/progress.js.map +0 -1
- package/dist/patterns/sorting.d.ts +0 -13
- package/dist/patterns/sorting.d.ts.map +0 -1
- package/dist/patterns/sorting.js +0 -10
- package/dist/patterns/sorting.js.map +0 -1
- package/dist/patterns/status.d.ts +0 -15
- package/dist/patterns/status.d.ts.map +0 -1
- package/dist/patterns/status.js +0 -9
- package/dist/patterns/status.js.map +0 -1
- package/dist/patterns/timestamps.d.ts +0 -10
- package/dist/patterns/timestamps.d.ts.map +0 -1
- package/dist/patterns/timestamps.js +0 -10
- package/dist/patterns/timestamps.js.map +0 -1
- package/dist/provision-config.d.ts +0 -22
- package/dist/provision-config.d.ts.map +0 -1
- package/dist/provision-config.js +0 -210
- package/dist/provision-config.js.map +0 -1
- package/dist/provision.d.ts +0 -71
- package/dist/provision.d.ts.map +0 -1
- package/dist/provision.js +0 -56
- package/dist/provision.js.map +0 -1
- package/dist/redaction/index.d.ts +0 -4
- package/dist/redaction/index.d.ts.map +0 -1
- package/dist/redaction/index.js +0 -3
- package/dist/redaction/index.js.map +0 -1
- package/dist/redaction/patterns.d.ts +0 -9
- package/dist/redaction/patterns.d.ts.map +0 -1
- package/dist/redaction/patterns.js +0 -39
- package/dist/redaction/patterns.js.map +0 -1
- package/dist/redaction/redactor.d.ts +0 -27
- package/dist/redaction/redactor.d.ts.map +0 -1
- package/dist/redaction/redactor.js +0 -89
- package/dist/redaction/redactor.js.map +0 -1
- package/dist/resilience.d.ts +0 -34
- package/dist/resilience.d.ts.map +0 -1
- package/dist/resilience.js +0 -164
- package/dist/resilience.js.map +0 -1
- package/dist/result.d.ts +0 -57
- package/dist/result.d.ts.map +0 -1
- package/dist/result.js +0 -156
- package/dist/result.js.map +0 -1
- package/dist/run.d.ts +0 -27
- package/dist/run.d.ts.map +0 -1
- package/dist/run.js +0 -34
- package/dist/run.js.map +0 -1
- package/dist/serialization.d.ts +0 -27
- package/dist/serialization.d.ts.map +0 -1
- package/dist/serialization.js +0 -153
- package/dist/serialization.js.map +0 -1
- package/dist/service-config.d.ts +0 -22
- package/dist/service-config.d.ts.map +0 -1
- package/dist/service-config.js +0 -210
- package/dist/service-config.js.map +0 -1
- package/dist/service.d.ts +0 -71
- package/dist/service.d.ts.map +0 -1
- package/dist/service.js +0 -56
- package/dist/service.js.map +0 -1
- package/dist/signal.d.ts +0 -33
- package/dist/signal.d.ts.map +0 -1
- package/dist/signal.js +0 -17
- package/dist/signal.js.map +0 -1
- package/dist/topo-store.d.ts +0 -48
- package/dist/topo-store.d.ts.map +0 -1
- package/dist/topo-store.js +0 -175
- package/dist/topo-store.js.map +0 -1
- package/dist/topo.d.ts +0 -25
- package/dist/topo.d.ts.map +0 -1
- package/dist/topo.js +0 -112
- package/dist/topo.js.map +0 -1
- package/dist/trail.d.ts +0 -96
- package/dist/trail.d.ts.map +0 -1
- package/dist/trail.js +0 -20
- package/dist/trail.js.map +0 -1
- package/dist/type-utils.d.ts +0 -24
- package/dist/type-utils.d.ts.map +0 -1
- package/dist/type-utils.js +0 -12
- package/dist/type-utils.js.map +0 -1
- package/dist/types.d.ts +0 -71
- package/dist/types.d.ts.map +0 -1
- package/dist/types.js +0 -3
- package/dist/types.js.map +0 -1
- package/dist/validate-established-topo.d.ts +0 -76
- package/dist/validate-established-topo.d.ts.map +0 -1
- package/dist/validate-established-topo.js +0 -43
- package/dist/validate-established-topo.js.map +0 -1
- package/dist/validate-topo.d.ts +0 -24
- package/dist/validate-topo.d.ts.map +0 -1
- package/dist/validate-topo.js +0 -176
- package/dist/validate-topo.js.map +0 -1
- package/dist/validation.d.ts +0 -27
- package/dist/validation.d.ts.map +0 -1
- package/dist/validation.js +0 -165
- package/dist/validation.js.map +0 -1
- package/dist/workspace.d.ts +0 -25
- package/dist/workspace.d.ts.map +0 -1
- package/dist/workspace.js +0 -57
- package/dist/workspace.js.map +0 -1
- package/src/__tests__/blob-ref.test.ts +0 -103
- package/src/__tests__/branded.test.ts +0 -148
- package/src/__tests__/collections.test.ts +0 -126
- package/src/__tests__/context.test.ts +0 -77
- package/src/__tests__/derive.test.ts +0 -260
- package/src/__tests__/errors.test.ts +0 -309
- package/src/__tests__/execute.test.ts +0 -525
- package/src/__tests__/fetch.test.ts +0 -217
- package/src/__tests__/gate.test.ts +0 -125
- package/src/__tests__/guards.test.ts +0 -102
- package/src/__tests__/path-security.test.ts +0 -114
- package/src/__tests__/patterns.test.ts +0 -273
- package/src/__tests__/redaction.test.ts +0 -244
- package/src/__tests__/resilience.test.ts +0 -246
- package/src/__tests__/result.test.ts +0 -155
- package/src/__tests__/run.test.ts +0 -181
- package/src/__tests__/serialization.test.ts +0 -401
- package/src/__tests__/service-config.test.ts +0 -228
- package/src/__tests__/service.test.ts +0 -197
- package/src/__tests__/signal.test.ts +0 -82
- package/src/__tests__/topo-store-read.test.ts +0 -251
- package/src/__tests__/topo-store.test.ts +0 -469
- package/src/__tests__/topo.test.ts +0 -277
- package/src/__tests__/trail-permit.test.ts +0 -60
- package/src/__tests__/trail.test.ts +0 -261
- package/src/__tests__/trails-db.test.ts +0 -191
- package/src/__tests__/type-utils.test.ts +0 -90
- package/src/__tests__/validate-topo.test.ts +0 -489
- package/src/__tests__/validation.test.ts +0 -336
- package/src/__tests__/workspace.test.ts +0 -183
- package/src/event.ts +0 -15
- package/src/gate.ts +0 -44
- package/src/internal/topo-saves.ts +0 -429
- package/src/internal/topo-store-read.ts +0 -473
- package/src/internal/topo-store.ts +0 -1087
- package/src/provision-config.ts +0 -373
- package/src/provision.ts +0 -148
- package/src/topo-store.ts +0 -301
- package/tsconfig.json +0 -9
- package/tsconfig.tsbuildinfo +0 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,79 @@
|
|
|
1
1
|
# @ontrails/core
|
|
2
2
|
|
|
3
|
+
## 1.0.0-beta.16
|
|
4
|
+
|
|
5
|
+
### Major Changes
|
|
6
|
+
|
|
7
|
+
- 331e3a9: Relocate the topo-store public API from `@ontrails/core` to `@ontrails/topographer` per ADR-0042. Generic `trails-db` helpers (`openReadTrailsDb`, `openWriteTrailsDb`, `ensureSubsystemSchema`, `deriveTrailsDbPath`, `deriveTrailsDir`) stay in core because tracing and other subsystems share them.
|
|
8
|
+
|
|
9
|
+
Breaking pre-1.0 beta change. Update consumer imports:
|
|
10
|
+
|
|
11
|
+
```diff
|
|
12
|
+
- import { topoStore, createTopoStore, createMockTopoStore, createTopoSnapshot, listTopoSnapshots, pinTopoSnapshot, unpinTopoSnapshot, createStoredTopoSnapshot, getStoredTopoExport, countTopoSnapshots, countPinnedSnapshots, countPrunableSnapshots, pruneUnpinnedSnapshots } from '@ontrails/core';
|
|
13
|
+
+ import { topoStore, createTopoStore, createMockTopoStore, createTopoSnapshot, listTopoSnapshots, pinTopoSnapshot, unpinTopoSnapshot } from '@ontrails/topographer';
|
|
14
|
+
+ import { createStoredTopoSnapshot, getStoredTopoExport, countTopoSnapshots, countPinnedSnapshots, countPrunableSnapshots, pruneUnpinnedSnapshots } from '@ontrails/topographer/backend-support';
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
The same root move applies to types `ReadOnlyTopoStore`, `MockTopoStoreSeed`, `TopoSnapshot`, `TopoStoreRef`, `TopoStoreExportRecord`, `TopoStoreResourceRecord`, `TopoStoreTrailRecord`, `TopoStoreTrailDetailRecord`, `CreateTopoSnapshotInput`, and `ListTopoSnapshotsOptions`. The direct DB helper type `StoredTopoExport` moves to `@ontrails/topographer/backend-support`.
|
|
18
|
+
|
|
19
|
+
Core newly exports `activationSourceKey`, `projectActivationSourceDeclaration`, `activationSourceDeclarationSignature`, and the `ActivationSourceProjection` type — these were already used internally and are now part of the public surface so `@ontrails/topographer` (the only consumer that needs them) can import them through normal package channels.
|
|
20
|
+
|
|
21
|
+
- 10eae9a: Migrate the Trails workspace to the documented `.trails/` layout: committed `.lock` files at the workspace root, ignored `cache/` for rebuildable derived data, ignored `state/` for mutable runtime state, and `.trails/config.local.{ts,js}` for local overrides. The default SQLite path is now `.trails/state/trails.db`. Workspace bootstrap creates only `cache/` and `state/` — the legacy `dev/` and `generated/` subdirectories are no longer created. Dev reset cleans both the new `.trails/state/` paths and legacy `.trails/trails.db*` and `.trails/dev/tracing.db*` paths for one cycle. Scaffold and workspace gitignores reflect the new layout.
|
|
22
|
+
|
|
23
|
+
Workspace bootstrap is now owned by a single canonical source in `@ontrails/core`. The package exposes `ensureTrailsWorkspace()`, `WORKSPACE_GITIGNORE_CONTENT`, and `WORKSPACE_GITIGNORE_LINES`. `@ontrails/config` no longer exports its own `ensureWorkspace` (consumers should import from `@ontrails/core`). `trails create` now writes `.trails/.gitignore` during scaffolding so a fresh-scaffolded project's initial commit includes the workspace gitignore (resolves TRL-703).
|
|
24
|
+
|
|
25
|
+
### Minor Changes
|
|
26
|
+
|
|
27
|
+
- bcdc484: Add an explicit `unmockable: { reason }` resource marker and have testing auto-mock resolution skip intentionally unmockable resources.
|
|
28
|
+
- 4b8d13b: **BREAKING:** Complete the `trailhead` to `surface` public API cutover in core.
|
|
29
|
+
|
|
30
|
+
- `TraceRecord.trailhead` is now `TraceRecord.surface`.
|
|
31
|
+
- `SURFACE_KEY` now uses the `__trails_surface` extension key value, and the deprecated `TRAILHEAD_KEY` alias is removed.
|
|
32
|
+
- Deprecated `transport*` surface-error aliases are removed; import the existing `surface*` names instead.
|
|
33
|
+
- `isVisibleToTrailheads` is renamed to `isVisibleToSurfaces`.
|
|
34
|
+
|
|
35
|
+
See `docs/migration/trailhead-to-surface.md` for the full migration map.
|
|
36
|
+
|
|
37
|
+
- 112b9f2: Add `dryRun` to `TrailContext` and wire `--dry-run`. `TrailContext` gains an optional `dryRun?: boolean` field, defaulted to `false` in `createTrailContext`. `ExecuteTrailOptions` carries `dryRun?: boolean` through `applyContextOverrides` so `executeTrail(t, input, { dryRun: true })` produces `ctx.dryRun === true`. The CLI's existing `dryRunPreset` (auto-derived for `intent: 'write' | 'destroy'` trails) now flows through `META_FLAG_CANDIDATES` and reaches the executor via `runTrailOnce`, so `trails run booking.cancel … --dry-run` lands `ctx.dryRun === true` on the trail's blaze. Trails that don't read the field are unchanged. Read-intent trails don't get `--dry-run` exposed.
|
|
38
|
+
- 893025e: Add `--permit '<json>'` to inject an inline permit on `trails run`. New `permitPreset()` exposes a `--permit` string flag that the CLI build parses and validates against the `BasePermit` shape (`{ id: string, scopes: string[] }`) using a small Zod schema. Valid permits flow through `ExecuteTrailOptions.permit` → `applyContextOverrides` → `ctx.permit` so existing `enforcePermitRequirement` behavior just sees a populated permit. Invalid JSON or schema mismatch surface as `Result.err(ValidationError)` (exit code 1) before the trail runs, avoiding spurious `PermitError` results from malformed input. The flag is global, never routed into trail input (added to `META_FLAG_CANDIDATES`), and overlays only when defined.
|
|
39
|
+
|
|
40
|
+
Topographer now projects permit requirements into surface-map entries and classifies permit-tightening diffs as breaking when new scopes are required.
|
|
41
|
+
|
|
42
|
+
- eec5e9d: Default `ctx.logger` to a structured stdout console sink when `topo()` is called without an `observe:` option. Apps now get observability for free with zero configuration, per ADR-0041. Explicit `observe:` values (including `combine()` with no sinks, an explicit `Logger`, or an explicit `{ log }` config) are preserved untouched — the default is only injected when no `observe:` is supplied.
|
|
43
|
+
- ebd4434: Phase 7 starts: typed layers with optional object-shaped surface input. `Layer` gains an optional `input?: LayerInputSchema` field for surface projection (TRL-473/474 will project it onto CLI/MCP/HTTP). `executeTrail({ layers })` remains the canonical per-call wrapper option. Layers without `input` schemas stay surface-invisible and cover runtime-only concerns such as tenant guards, rate limiting, circuit breaking, and custom audit logging.
|
|
44
|
+
- 863d473: Add three attachment scopes for typed layers — trail, surface, topo — with composition order **topo → surface → trail → blaze**. `TrailSpec` and `Trail` gain `layers?: readonly Layer[]` (default `[]`). `topo()` accepts `{ layers: [...] }` as the third options argument; the topo carries those layers and they reach the executor via `ExecuteTrailOptions.topoLayers`. The CLI's `surface()`/`createProgram()`/`deriveCliCommands` already supports a `layers` option; that now flows through `runTrailOnce` as `surfaceLayers`. The executor builds the layer chain `[...topoLayers, ...surfaceLayers, ...trail.layers, ...options.layers]` so topo wraps surface wraps trail wraps blaze (verified by composition-order tests at every level). Survey's `TrailDetailReport` adds `composedLayers: { topo, surface, trail }` so agents can introspect the layer chain per trail. Backward-compatible: every new field is optional with a non-undefined default; existing call sites are unchanged.
|
|
45
|
+
- 344f2f7: Project typed-layer `input` schemas onto CLI flags. Each effective layer (topo + surface + trail composition order) with a non-undefined `input` schema gets its fields auto-derived into `--flag` options on every command it attaches to. Parsed values route to the layer at runtime via `ctx.extensions[LAYER_INPUTS_KEY][layer.name]` — `Layer.wrap` is unchanged. Collision rule: if a layer field name collides with a trail input field, another layer's projected name, or a CLI meta flag, the layer's flag is renamed to `--<layer-name>-<original-flag-name>` and a one-line warning emits to stderr (`[trails] ...`). Renames are deterministic across builds. New `LAYER_INPUTS_KEY` (exported from `@ontrails/core`) reserves the `ctx.extensions` slot.
|
|
46
|
+
- 26f9ffd: Project typed-layer `input` schemas onto MCP and HTTP surfaces. Closes Phase 7. Lifts `collectAttachedTypedLayers` and `projectLayerFieldName` (collision-rename rule) into `@ontrails/core/internal/layer-projection` so all three surfaces share one source of truth. The CLI surface refactors to consume the lifted helpers (no behavior change). MCP merges layer fields into each tool's `inputSchema` and partitions inbound args at invocation time. HTTP merges layer fields into the route's request schema (query for reads, body for writes) and exposes new optional `HttpRouteDefinition.inputSchema` + `layerInputProjections` for surface adapters / OpenAPI generators. Collision rule matches TRL-473's: deterministic rename to a layer-prefixed camelCase name with the original captured in the routing table. Side fix: MCP and HTTP handlers now forward `topoLayers: graph.layers` + `surfaceLayers: layers` so topo-scope layers actually compose at runtime (previously the handlers used the deprecated `layers` alias and never read `graph.layers`).
|
|
47
|
+
- 22c6c06: Accept ADR-0041 Unified Observability and ship the first activation and
|
|
48
|
+
observability primitives it depends on: activation trace records, topo-level
|
|
49
|
+
observe configuration, webhook activation materialization, signal/webhook
|
|
50
|
+
warden coaching, the `@ontrails/observe` package, sink composition, and
|
|
51
|
+
zero-dependency observe sinks.
|
|
52
|
+
|
|
53
|
+
### Patch Changes
|
|
54
|
+
|
|
55
|
+
- 73622ae: Thread `ResourceSpec.config` through the built-in auth resource. Resource config schemas that accept `undefined` now receive their parsed default when config values are omitted, and `authResource` can materialize the no-op or JWT adapter from typed config while preserving existing mock and override paths.
|
|
56
|
+
- 6300f70: Refresh source comments and test labels for retired connector terminology as adapter guardrails become strict.
|
|
57
|
+
- d172013: Preserve specialized TrailsError identity and retry-exhaustion metadata when
|
|
58
|
+
serializing and deserializing framework errors.
|
|
59
|
+
- c3fc5c3: Move previously root-exported helper contracts out of `src/internal/*` to stable core module homes, document their public boundary, and guard the public barrel against future internal re-exports.
|
|
60
|
+
- 20d7a5c: Enforce the shared safe error projection policy for public error bodies, diagnostics, serialized payloads, and CLI stderr.
|
|
61
|
+
- be5fb46: Publish registry-driven error taxonomy documentation and checks from the core error registry.
|
|
62
|
+
- e898cc4: Add repo-level Knip dead-code detection and remove stale internal exports and unused package dependencies surfaced by the new check.
|
|
63
|
+
- 3395234: Move store adapter-binding helpers to `@ontrails/store/adapter-support` and topographer direct database/admin helpers to `@ontrails/topographer/backend-support`, keeping root exports focused on contract-level APIs.
|
|
64
|
+
- 4399fdb: Renamed `@ontrails/schema` to `@ontrails/topographer`. Mechanical rename only — no API changes. Update import sites from `@ontrails/schema` to `@ontrails/topographer`. See ADR-0042 for the durable graph substrate doctrine.
|
|
65
|
+
|
|
66
|
+
## 1.0.0-beta.15
|
|
67
|
+
|
|
68
|
+
### Minor Changes
|
|
69
|
+
|
|
70
|
+
- 4ad6b25: Lexicon rename cleanup (ADR-0023). Breaking for `@ontrails/core`, `@ontrails/cli`, and `@ontrails/tracing` at the boundary; internal-only churn for `@ontrails/warden`.
|
|
71
|
+
|
|
72
|
+
- **core**: the topo store schema renames `topo_provisions` / `topo_trail_provisions` → `topo_resources` / `topo_trail_resources` and `provision_count` → `resource_count`. Schema version bumped v4→v5. Stores still carrying the legacy schema are detected on open, dropped, and recreated from the new DDL — previous topo saves are cleared. Stored-data helpers `listTopoStoreProvisions` / `getTopoStoreProvision` / `readProvisionUsage` / `mapProvisionRow` renamed to their `resource` counterparts. TS row types `TopoTrailProvisionRow` / `TopoProvisionRow` renamed to `TopoTrailResourceRow` / `TopoResourceRow`.
|
|
73
|
+
- **cli**: CLI output mode env vars are now derived from the topo name per ADR-0023. Legacy globals `TRAILS_JSON` / `TRAILS_JSONL` are no longer honored — a topo named `stash` reads `STASH_JSON` / `STASH_JSONL`. `ActionResultContext` gains a `topoName: string` field; `resolveOutputMode(flags, topoName)` takes a topo name argument.
|
|
74
|
+
- **tracing**: legacy `.trails/dev/tracker.db` migration path removed. Any user still running a pre-rename beta build with a `tracker.db` should delete it or migrate before upgrading.
|
|
75
|
+
- **warden**: internal-only rename of `provisionDeclarations` / `provisionExists` rules and their trails to `resourceDeclarations` / `resourceExists`. No behavior change.
|
|
76
|
+
|
|
3
77
|
## 1.0.0-beta.14
|
|
4
78
|
|
|
5
79
|
### Minor Changes
|
package/README.md
CHANGED
|
@@ -18,7 +18,7 @@ const greet = trail('greet', {
|
|
|
18
18
|
blaze: (input) => Result.ok({ message: `Hello, ${input.name}!` }),
|
|
19
19
|
});
|
|
20
20
|
|
|
21
|
-
const
|
|
21
|
+
const graph = topo('myapp', { greet });
|
|
22
22
|
```
|
|
23
23
|
|
|
24
24
|
Trails compose other trails through `crosses` and `ctx.cross()`:
|
|
@@ -43,23 +43,26 @@ const onboard = trail('entity.onboard', {
|
|
|
43
43
|
| --- | --- |
|
|
44
44
|
| `trail(id, spec)` | Define a unit of work with typed input and `Result` output; use `crosses` for composition |
|
|
45
45
|
| `signal(id, spec)` | Define a server-originated notification with a typed data schema |
|
|
46
|
-
| `
|
|
47
|
-
| `
|
|
46
|
+
| `resource(id, spec)` | Define an infrastructure dependency with `create`, `dispose`, and optional `mock` |
|
|
47
|
+
| `drainResources(resources, ctx, configValues?)` | Evict and dispose cached resource singletons for surface/test shutdown |
|
|
48
|
+
| `blobRefSchema` / `createBlobRef(...)` | Declare and create binary output references with a shared descriptor contract |
|
|
49
|
+
| `topo(name, ...modules, options?)` | Collect trail modules into a queryable topology with optional `observe:` sinks |
|
|
50
|
+
| `deriveTrail(contour, operation, spec)` | Derive CRUD-shaped trail contracts from a contour on the `@ontrails/core/trails` subpath |
|
|
48
51
|
| `validateTopo(topo)` | Structural validation: cross targets exist, no cycles, examples parse, output schemas present |
|
|
49
52
|
|
|
50
53
|
### Execution
|
|
51
54
|
|
|
52
55
|
| Export | What it does |
|
|
53
56
|
| --- | --- |
|
|
54
|
-
| `executeTrail(trail, rawInput, options?)` | Centralized execution pipeline: validates input, builds context, composes
|
|
57
|
+
| `executeTrail(trail, rawInput, options?)` | Centralized execution pipeline: validates input, builds context, composes layers, runs the implementation. Never throws -- exceptions become `Result.err(InternalError)`. |
|
|
55
58
|
| `run(topo, id, input, options?)` | Headless trail execution by ID. Looks up the trail in the topo, then delegates to `executeTrail`. Returns `Result.err(NotFoundError)` if the ID is not registered. |
|
|
56
59
|
|
|
57
60
|
```typescript
|
|
58
|
-
// executeTrail —
|
|
61
|
+
// executeTrail — surfaces use this directly
|
|
59
62
|
const result = await executeTrail(greet, { name: 'Alice' });
|
|
60
63
|
|
|
61
|
-
// run —
|
|
62
|
-
const result = await run(
|
|
64
|
+
// run — headless execution by trail ID
|
|
65
|
+
const result = await run(graph, 'greet', { name: 'Alice' });
|
|
63
66
|
if (result.isOk()) console.log(result.value);
|
|
64
67
|
```
|
|
65
68
|
|
|
@@ -89,7 +92,7 @@ Beyond the `trail(id, spec)` builder, `Topo` exposes these accessors:
|
|
|
89
92
|
|
|
90
93
|
| Type | What it describes |
|
|
91
94
|
| --- | --- |
|
|
92
|
-
| `ExecuteTrailOptions` | Options for `executeTrail`: `ctx`, `abortSignal`, `
|
|
95
|
+
| `ExecuteTrailOptions` | Options for `executeTrail`: `ctx`, `abortSignal`, `layers`, `createContext` |
|
|
93
96
|
| `RunOptions` | Same shape as `ExecuteTrailOptions`; forwarded by `run` |
|
|
94
97
|
|
|
95
98
|
### Result
|
|
@@ -112,38 +115,84 @@ result.unwrapOr(fallback); // Value or fallback
|
|
|
112
115
|
|
|
113
116
|
### Error taxonomy
|
|
114
117
|
|
|
115
|
-
|
|
118
|
+
The current taxonomy is generated from the `errorClasses` owner registry and
|
|
119
|
+
category code maps in `@ontrails/core`.
|
|
116
120
|
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
| `validation` | `ValidationError`, `AmbiguousError`, `AssertionError` | 400 | No |
|
|
120
|
-
| `not_found` | `NotFoundError` | 404 | No |
|
|
121
|
-
| `conflict` | `AlreadyExistsError`, `ConflictError` | 409 | No |
|
|
122
|
-
| `permission` | `PermissionError` | 403 | No |
|
|
123
|
-
| `timeout` | `TimeoutError` | 504 | Yes |
|
|
124
|
-
| `rate_limit` | `RateLimitError` | 429 | Yes |
|
|
125
|
-
| `network` | `NetworkError` | 502 | Yes |
|
|
126
|
-
| `internal` | `InternalError` | 500 | No |
|
|
127
|
-
| `auth` | `AuthError` | 401 | No |
|
|
128
|
-
| `cancelled` | `CancelledError` | 499 | No |
|
|
121
|
+
<!-- error-taxonomy:start -->
|
|
122
|
+
<!-- GENERATED: run `bun run error-taxonomy:sync`; check with `bun run error-taxonomy:check`. Variant: category. -->
|
|
129
123
|
|
|
130
|
-
|
|
124
|
+
| Category | CLI Exit | HTTP | JSON-RPC | Retryable | Fixed Classes |
|
|
125
|
+
| --- | --- | --- | --- | --- | --- |
|
|
126
|
+
| `validation` | 1 | 400 | -32602 | No | `ValidationError`, `AmbiguousError` |
|
|
127
|
+
| `not_found` | 2 | 404 | -32601 | No | `NotFoundError` |
|
|
128
|
+
| `conflict` | 3 | 409 | -32603 | No | `AlreadyExistsError`, `ConflictError` |
|
|
129
|
+
| `permission` | 4 | 403 | -32600 | No | `PermissionError`, `PermitError` |
|
|
130
|
+
| `timeout` | 5 | 504 | -32603 | Yes | `TimeoutError` |
|
|
131
|
+
| `rate_limit` | 6 | 429 | -32603 | Yes | `RateLimitError` |
|
|
132
|
+
| `network` | 7 | 502 | -32603 | Yes | `NetworkError` |
|
|
133
|
+
| `internal` | 8 | 500 | -32603 | No | `AssertionError`, `InternalError`, `DerivationError`, `RecoverableCompletionError` |
|
|
134
|
+
| `auth` | 9 | 401 | -32600 | No | `AuthError` |
|
|
135
|
+
| `cancelled` | 130 | 499 | -32603 | No | `CancelledError` |
|
|
136
|
+
|
|
137
|
+
Dynamic classes:
|
|
138
|
+
|
|
139
|
+
- `RetryExhaustedError` inherits category and surface codes from its wrapped `TrailsError`; retryable is always No.
|
|
140
|
+
<!-- error-taxonomy:end -->
|
|
141
|
+
|
|
142
|
+
Public surface projections redact sensitive substrings before exposing a
|
|
143
|
+
non-internal `TrailsError` message. Internal-category `TrailsError` instances and
|
|
144
|
+
unknown native errors project with the generic message `Internal server error`;
|
|
145
|
+
diagnostics and serialized payloads keep their useful structure while redacting
|
|
146
|
+
messages, context, and stack strings.
|
|
147
|
+
|
|
148
|
+
The developer returns `Result.err(new NotFoundError(...))`. The framework maps it to the right code on every surface.
|
|
131
149
|
|
|
132
150
|
### Other exports
|
|
133
151
|
|
|
134
152
|
- **Schema derivation** -- `deriveFields(schema)` extracts faithfully
|
|
135
153
|
representable field metadata from Zod for prompts and forms
|
|
136
154
|
- **Validation** -- `validateInput`, `formatZodIssues`, `zodToJsonSchema`
|
|
137
|
-
- **Resilience** -- `retry`, `withTimeout`, `shouldRetry`, `
|
|
155
|
+
- **Resilience** -- `retry`, `withTimeout`, `shouldRetry`, `deriveBackoffDelay`
|
|
138
156
|
- **Serialization** -- `serializeError`, `deserializeError`
|
|
139
157
|
- **Branded types** -- `uuid`, `email`, `nonEmptyString`, `positiveInt`
|
|
140
|
-
- **
|
|
158
|
+
- **Execution layers** -- low-level pipeline wrappers via `composeLayers`
|
|
141
159
|
- **Guards and collections** -- `isDefined`, `chunk`, `dedupe`, `groupBy`, `sortBy`
|
|
142
160
|
- **Patterns** (`@ontrails/core/patterns`) -- reusable Zod schemas for pagination, bulk ops, timestamps, sorting
|
|
161
|
+
- **Trail factories** (`@ontrails/core/trails`) -- derive CRUD-shaped trail contracts from contours without re-authoring IDs, schemas, examples, or intents
|
|
143
162
|
- **Redaction** (`@ontrails/core/redaction`) -- strip sensitive data before logging
|
|
144
163
|
|
|
164
|
+
### Public helper boundaries
|
|
165
|
+
|
|
166
|
+
The root package also exposes a few low-level contracts that other framework
|
|
167
|
+
packages build on:
|
|
168
|
+
|
|
169
|
+
- **Intrinsic tracing** -- `TraceRecord`, `TraceSink`, `TraceContext`, and the
|
|
170
|
+
sink registry helpers are the core-owned execution record shape shared by
|
|
171
|
+
`@ontrails/observe`, `@ontrails/tracing`, and adapters.
|
|
172
|
+
- **Trails DB** -- `deriveTrailsDbPath`, `deriveTrailsDir`,
|
|
173
|
+
`ensureSubsystemSchema`, `openReadTrailsDb`, and `openWriteTrailsDb` are the
|
|
174
|
+
generic database primitive used by framework subsystems.
|
|
175
|
+
- **Surface projection helpers** -- safe error projection, layer field
|
|
176
|
+
projection, cross-batch validation, late-bound signal references, and Zod
|
|
177
|
+
default-wrapper stripping are stable root exports for first-party surfaces,
|
|
178
|
+
store helpers, and tests.
|
|
179
|
+
|
|
145
180
|
See the [API Reference](../../docs/api-reference.md) for the full list.
|
|
146
181
|
|
|
182
|
+
## Migration: topo-store moved to `@ontrails/topographer`
|
|
183
|
+
|
|
184
|
+
Per [ADR-0042](../../docs/adr/0042-core-topographer-boundary-doctrine.md), the topo-store public API previously exported from `@ontrails/core` now lives in `@ontrails/topographer`. Generic `trails-db` helpers (`openReadTrailsDb`, `openWriteTrailsDb`, `ensureSubsystemSchema`, `deriveTrailsDbPath`, `deriveTrailsDir`) stay in core because tracing and other subsystems share them.
|
|
185
|
+
|
|
186
|
+
Update consumer imports:
|
|
187
|
+
|
|
188
|
+
```diff
|
|
189
|
+
- import { topoStore, createTopoStore, createMockTopoStore, createTopoSnapshot, listTopoSnapshots, pinTopoSnapshot, unpinTopoSnapshot, createStoredTopoSnapshot, getStoredTopoExport, countTopoSnapshots, countPinnedSnapshots, countPrunableSnapshots, pruneUnpinnedSnapshots } from '@ontrails/core';
|
|
190
|
+
+ import { topoStore, createTopoStore, createMockTopoStore, createTopoSnapshot, listTopoSnapshots, pinTopoSnapshot, unpinTopoSnapshot } from '@ontrails/topographer';
|
|
191
|
+
+ import { createStoredTopoSnapshot, getStoredTopoExport, countTopoSnapshots, countPinnedSnapshots, countPrunableSnapshots, pruneUnpinnedSnapshots } from '@ontrails/topographer/backend-support';
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
Types `ReadOnlyTopoStore`, `MockTopoStoreSeed`, `TopoSnapshot`, `TopoStoreRef`, `TopoStoreExportRecord`, `TopoStoreResourceRecord`, `TopoStoreTrailRecord`, `TopoStoreTrailDetailRecord`, `CreateTopoSnapshotInput`, and `ListTopoSnapshotsOptions` move to `@ontrails/topographer`. `StoredTopoExport` moves to `@ontrails/topographer/backend-support`.
|
|
195
|
+
|
|
147
196
|
## Installation
|
|
148
197
|
|
|
149
198
|
```bash
|
package/package.json
CHANGED
|
@@ -1,14 +1,21 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ontrails/core",
|
|
3
|
-
"version": "1.0.0-beta.
|
|
3
|
+
"version": "1.0.0-beta.16",
|
|
4
|
+
"files": [
|
|
5
|
+
"src/**/*.ts",
|
|
6
|
+
"!src/**/__tests__/**",
|
|
7
|
+
"!src/**/*.test.ts",
|
|
8
|
+
"!src/**/*.test-d.ts",
|
|
9
|
+
"README.md",
|
|
10
|
+
"CHANGELOG.md"
|
|
11
|
+
],
|
|
4
12
|
"type": "module",
|
|
5
13
|
"exports": {
|
|
6
14
|
".": "./src/index.ts",
|
|
7
|
-
"./internal/topo-saves": "./src/internal/topo-saves.ts",
|
|
8
|
-
"./internal/topo-store": "./src/internal/topo-store.ts",
|
|
9
|
-
"./internal/trails-db": "./src/internal/trails-db.ts",
|
|
10
15
|
"./patterns": "./src/patterns/index.ts",
|
|
11
16
|
"./redaction": "./src/redaction/index.ts",
|
|
17
|
+
"./store": "./src/store/index.ts",
|
|
18
|
+
"./trails": "./src/trails/index.ts",
|
|
12
19
|
"./package.json": "./package.json"
|
|
13
20
|
},
|
|
14
21
|
"scripts": {
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
ActivationSourceKind,
|
|
3
|
+
ActivationSourceMeta,
|
|
4
|
+
} from './activation-source.js';
|
|
5
|
+
|
|
6
|
+
export const ACTIVATION_PROVENANCE_KEY =
|
|
7
|
+
'__trails_activation_provenance' as const;
|
|
8
|
+
|
|
9
|
+
export interface ActivationProvenanceSource {
|
|
10
|
+
readonly cron?: string | undefined;
|
|
11
|
+
readonly id: string;
|
|
12
|
+
readonly kind: ActivationSourceKind;
|
|
13
|
+
readonly meta?: ActivationSourceMeta | undefined;
|
|
14
|
+
readonly producerTrailId?: string | undefined;
|
|
15
|
+
readonly timezone?: string | undefined;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export interface ActivationProvenance {
|
|
19
|
+
readonly fireId: string;
|
|
20
|
+
readonly parentFireId?: string | undefined;
|
|
21
|
+
readonly rootFireId: string;
|
|
22
|
+
readonly source: ActivationProvenanceSource;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export interface ActivationProvenanceCarrier {
|
|
26
|
+
readonly activation?: ActivationProvenance | undefined;
|
|
27
|
+
readonly extensions?: Readonly<Record<string, unknown>> | undefined;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const isObjectRecord = (value: unknown): value is Record<string, unknown> =>
|
|
31
|
+
typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
32
|
+
|
|
33
|
+
const optionalString = (value: unknown): boolean =>
|
|
34
|
+
value === undefined || typeof value === 'string';
|
|
35
|
+
|
|
36
|
+
const isActivationProvenanceSource = (
|
|
37
|
+
value: unknown
|
|
38
|
+
): value is ActivationProvenanceSource =>
|
|
39
|
+
isObjectRecord(value) &&
|
|
40
|
+
typeof value['id'] === 'string' &&
|
|
41
|
+
typeof value['kind'] === 'string' &&
|
|
42
|
+
optionalString(value['cron']) &&
|
|
43
|
+
optionalString(value['producerTrailId']) &&
|
|
44
|
+
optionalString(value['timezone']) &&
|
|
45
|
+
(value['meta'] === undefined || isObjectRecord(value['meta']));
|
|
46
|
+
|
|
47
|
+
const isActivationProvenance = (
|
|
48
|
+
value: unknown
|
|
49
|
+
): value is ActivationProvenance =>
|
|
50
|
+
isObjectRecord(value) &&
|
|
51
|
+
typeof value['fireId'] === 'string' &&
|
|
52
|
+
optionalString(value['parentFireId']) &&
|
|
53
|
+
typeof value['rootFireId'] === 'string' &&
|
|
54
|
+
isActivationProvenanceSource(value['source']);
|
|
55
|
+
|
|
56
|
+
export const getActivationProvenance = (
|
|
57
|
+
ctx: ActivationProvenanceCarrier | undefined
|
|
58
|
+
): ActivationProvenance | undefined => {
|
|
59
|
+
if (isActivationProvenance(ctx?.activation)) {
|
|
60
|
+
return ctx.activation;
|
|
61
|
+
}
|
|
62
|
+
const fromExtensions = ctx?.extensions?.[ACTIVATION_PROVENANCE_KEY];
|
|
63
|
+
return isActivationProvenance(fromExtensions) ? fromExtensions : undefined;
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
export const withActivationProvenance = <
|
|
67
|
+
TCtx extends {
|
|
68
|
+
readonly extensions?: Readonly<Record<string, unknown>> | undefined;
|
|
69
|
+
},
|
|
70
|
+
>(
|
|
71
|
+
ctx: TCtx,
|
|
72
|
+
activation: ActivationProvenance
|
|
73
|
+
): TCtx & { readonly activation: ActivationProvenance } => ({
|
|
74
|
+
...ctx,
|
|
75
|
+
activation,
|
|
76
|
+
extensions: {
|
|
77
|
+
...ctx.extensions,
|
|
78
|
+
[ACTIVATION_PROVENANCE_KEY]: activation,
|
|
79
|
+
},
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
export const buildActivationProvenanceTraceAttrs = (
|
|
83
|
+
activation: ActivationProvenance | undefined
|
|
84
|
+
): Readonly<Record<string, unknown>> => {
|
|
85
|
+
if (activation === undefined) {
|
|
86
|
+
return {};
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const attrs: Record<string, unknown> = {
|
|
90
|
+
'trails.activation.fire_id': activation.fireId,
|
|
91
|
+
'trails.activation.root_fire_id': activation.rootFireId,
|
|
92
|
+
'trails.activation.source.id': activation.source.id,
|
|
93
|
+
'trails.activation.source.kind': activation.source.kind,
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
if (activation.parentFireId !== undefined) {
|
|
97
|
+
attrs['trails.activation.parent_fire_id'] = activation.parentFireId;
|
|
98
|
+
}
|
|
99
|
+
if (activation.source.producerTrailId !== undefined) {
|
|
100
|
+
attrs['trails.activation.source.producer_trail.id'] =
|
|
101
|
+
activation.source.producerTrailId;
|
|
102
|
+
}
|
|
103
|
+
if (activation.source.cron !== undefined) {
|
|
104
|
+
attrs['trails.activation.source.cron'] = activation.source.cron;
|
|
105
|
+
}
|
|
106
|
+
if (activation.source.timezone !== undefined) {
|
|
107
|
+
attrs['trails.activation.source.timezone'] = activation.source.timezone;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
return attrs;
|
|
111
|
+
};
|