Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
dc722a0
chore(notion-cli): refresh pnpm deps hash
schickling-assistant Jun 11, 2026
b8f2ac5
feat(utils): add schema-first otel contracts
schickling-assistant Jun 11, 2026
1292d30
fix(utils): tighten otel attribute contracts
schickling-assistant Jun 11, 2026
f2edc83
chore(nix): refresh megarepo pnpm deps hash
schickling-assistant Jun 11, 2026
7dc6e8a
chore(nix): refresh tui-stories pnpm deps hash
schickling-assistant Jun 11, 2026
2a0ae46
chore(nix): refresh notion-md pnpm deps hash
schickling-assistant Jun 11, 2026
f6e2092
feat(otel): adopt schema-first span contracts
schickling-assistant Jun 11, 2026
f92cc92
Adopt schema-backed notion-md OTEL spans
schickling-assistant Jun 11, 2026
5a40004
Adopt schema-first Playwright OTEL helpers
schickling-assistant Jun 11, 2026
278b88e
feat(notion-datasource-sync): route otel through helpers
schickling-assistant Jun 11, 2026
b95e3f3
fix(otel): route remaining sync spans through contracts
schickling-assistant Jun 11, 2026
16cf7ca
fix(otel): keep pty name validation authoritative
schickling-assistant Jun 11, 2026
9929daa
chore(nix): refresh tui stories deps hash
schickling-assistant Jun 11, 2026
76fc500
chore(nix): refresh workflow report deps hash
schickling-assistant Jun 11, 2026
019ed6d
chore(nix): refresh notion md deps hash
schickling-assistant Jun 11, 2026
124b044
fix(restate-effect): route spans through otel contracts
schickling-assistant Jun 11, 2026
7537672
refactor(otel): route genie and megarepo spans through contracts
schickling-assistant Jun 11, 2026
ac69b9e
fix(notion-md): enforce schema-first otel spans
schickling-assistant Jun 11, 2026
bca965f
refactor(otel): enforce schema-backed utility spans
schickling-assistant Jun 11, 2026
732fc4b
chore(nix): refresh genie deps hash
schickling-assistant Jun 11, 2026
8cbcdd8
chore(nix): refresh workflow report deps hash
schickling-assistant Jun 11, 2026
f13052d
chore(nix): refresh megarepo deps hash
schickling-assistant Jun 11, 2026
e88d8a5
test(otel): enforce raw span boundary
schickling-assistant Jun 11, 2026
25f69a4
chore(nix): refresh tui stories deps hash
schickling-assistant Jun 11, 2026
14981b2
chore(nix): refresh notion md deps hash
schickling-assistant Jun 11, 2026
220e08f
test(notion-md): relax cli subprocess timeout
schickling-assistant Jun 11, 2026
76ebc3c
refactor(otel): adopt schema-first contracts
schickling-assistant Jun 11, 2026
1cc2853
chore(nix): refresh otel adoption deps hashes
schickling-assistant Jun 11, 2026
50c8013
chore(nix): refresh remaining pnpm deps hashes
schickling-assistant Jun 11, 2026
09da497
refactor(restate): centralize observability contracts
schickling-assistant Jun 11, 2026
4c2d713
docs(otel): remove raw span references
schickling-assistant Jun 11, 2026
57f7fef
test(pty): wait for peek output in live test
schickling-assistant Jun 11, 2026
2e289e8
test(restate): cover schema-first baseline metrics
schickling-assistant Jun 11, 2026
f57edd7
fix(otel): preserve typed sync contract errors
schickling-assistant Jun 11, 2026
1a34e49
fix(otelite): support explicit binary path
schickling-assistant Jun 11, 2026
65740f5
feat(otel): add metric bridge facade
schickling-assistant Jun 11, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 30 additions & 0 deletions .oxlintrc.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

27 changes: 27 additions & 0 deletions .oxlintrc.json.genie.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import {
baseOxlintOverrides,
baseOxlintPlugins,
baseOxlintRules,
otelOxlintRules,
} from './genie/oxlint-base.ts'
import { oxlintConfig, type OxlintConfigArgs } from './packages/@overeng/genie/src/runtime/mod.ts'

Expand All @@ -27,6 +28,32 @@ export default oxlintConfig({
files: ['**/genie/src/runtime/**/*.test.ts'],
rules: { 'overeng/no-external-imports': 'off' },
},
// effect-utils: production code must use schema-backed OTEL contracts instead
// of raw Effect/Stream span primitives. Keep boundary/runtime/test exceptions
// narrow and explicit so repo-wide adoption remains mechanically checkable.
{
files: ['packages/@overeng/*/src/**/*.ts', 'packages/@overeng/*/src/**/*.tsx'],
rules: otelOxlintRules({ rawOtel: 'error' }),
},
{
files: [
'packages/@overeng/otel-contract/src/**',
'packages/@overeng/utils-dev/src/otelite/**',
'packages/@overeng/*/src/**/*.test.ts',
'packages/@overeng/*/src/**/*.test.tsx',
'packages/@overeng/*/src/**/*.spec.ts',
'packages/@overeng/*/src/**/*.spec.tsx',
'packages/@overeng/*/src/**/*.unit.test.ts',
'packages/@overeng/*/src/**/*.unit.test.tsx',
'packages/@overeng/*/src/**/*.integration.test.ts',
'packages/@overeng/*/src/**/*.integration.test.tsx',
'packages/@overeng/*/src/**/*.e2e.test.ts',
'packages/@overeng/*/src/**/*.e2e.test.tsx',
'packages/@overeng/*/src/**/*.gen.ts',
'packages/@overeng/*/src/**/*.gen.tsx',
],
rules: otelOxlintRules({ rawOtel: 'off' }),
},
// restate-effect: ban raw nondeterminism in SOURCE handler code (R20, decision
// 0004). The journaled Clock/Random + explicit durable combinators are the
// primary guarantee; this lint is an advisory backstop. Scoped to `src/` only —
Expand Down
12 changes: 12 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,16 @@ All notable changes to this project will be documented in this file.

### Changed

- **@overeng/utils-dev/otelite**: Resolve the `otelite` binary from `OTELITE_BIN` before falling back to `PATH`, and document the plain-shell Nix workflow for focused wrapper tests.

- **@overeng/otel-contract**: Add branded/refined OTEL name schemas (`OtelAttributeKey`, `OtelSpanName`, `OtelMetricName`, `OtelServiceName`), validate contract names/keys at definition time, add an Effect `Metric` runtime bridge for schema-first metric contracts, and extend the raw-OTEL lint rule to ban raw Effect `Metric.*` APIs outside approved contract/test boundaries.

- **OTEL devenv module**: Stop requiring the retired legacy `otel` CLI in `OTEL_MODE=system`; dashboard refresh is now best-effort when a compatible legacy CLI is present, while shell setup still succeeds with the shared system stack.

- **@overeng/otelite-effect → @overeng/utils-dev/otelite**: Folded the standalone `@overeng/otelite-effect` wrapper package into `@overeng/utils-dev` as a new `./otelite` subpath export and deleted the standalone package. The wrapper is a dev/test util with no non-test consumer, and `utils-dev` already declares every dependency it needs (`@effect/platform`, `@effect/platform-node`, `@effect/opentelemetry`, `@effect/vitest`) and already uses subpath exports — co-locating it removes a `utils-dev ⇄ otelite-effect` cycle. The source moved to `packages/@overeng/utils-dev/src/otelite/` and the tests (incl. the D1 wire-level e2e) to `src/otelite/*.test.ts`. The public API is unchanged; consumers now import from `@overeng/utils-dev/otelite`. The `Otelite` service tag and the `Otelite*` error tags are renamespaced to `@overeng/utils-dev/otelite/*`. The tests still run the real nix-built `otelite` binary on `PATH` (provided by the dev shell). See `context/otelite/decisions/0015`.

- **CI / Nix packages**: Refresh the stale `genie`, `megarepo`, `notion-md`, and `tui-stories` pnpm fixed-output hashes after the schema-first OTEL contract change updated the workspace dependency closure.

- **CI / Nix packages**: Refresh the stale `workflow-report` pnpm fixed-output hash so the Storybook preview reporting step can build `#workflow-report` again after the branch rebase updated the workspace dependency closure.

- **@overeng/restate-effect**: Made `Restate.run`'s type HONEST. A durable `ctx.run` step carries NO catchable typed failure: the inner effect runs via `Runtime.runPromise` inside `ctx.run`, so a typed `Effect.fail` only REJECTS the step (Restate retries; a give-up maps to a `RestateError` DEFECT) and never reaches the outer failure channel — the old `run<A, E, R>(…): Effect<A, E, …>` advertised a typed `E` that `catchTag`/`catchAll` would typecheck against but that could never fire. `run` is now `run<A, R>(name, effect: Effect<A, never, R>, options?): Effect<A, never, …>`, and `runExit` is `runExit<A, R>(…): Effect<Exit<A>, never, …>` — the honest OBSERVATION form, whose failure channel is `never` (an observed failure is a defect/interrupt `Cause`, not a phantom typed `E`). Domain errors now belong in the HANDLER body (classify the step's result there) or are encoded as VALUES inside the step; to force a durable retry, DIE inside the step. A passed typed-`E` inner effect is now a COMPILE error (negative-type assertion in `capability-inference.types.ts`). Callers reconciled: the saga integration test's failing `pay` step `Effect.die`s (was `Effect.fail`), and `examples/12-self-reschedule.ts`'s `pollComposedSource` returns a tagged VALUE with `E = never` (classified in the cycle body, unchanged). `examples/14-http-error-classification.ts` already used the die-the-step / classify-in-body strategies; only its prose was corrected. VRS: decision 0003 (#4 — corrects the earlier "keep the inner `E` flowing through `run`"), 03-effect-runtime / 04-error-boundary specs, the guide handbook, and a DEFERRED typed-failure-transport `run` note (an encoded `fail(E)` journaled via an error schema). No dependency changes.
Expand All @@ -22,6 +30,10 @@ All notable changes to this project will be documented in this file.

### Added

- **@overeng/otel-contract**: Add the schema-first OTEL operation and metric-contract DSL (`OtelOperation`, `OtelMetric`, `OtelSpan.withStream`, attribute builders, compiled metadata, `encodeSync`, and checked dynamic span-map annotations) and migrate product instrumentation across the repo off raw `Effect.withSpan` / `Stream.withSpan` / `Effect.annotateCurrentSpan` and normal `unsafe*` contract calls. The contract remains runtime-light: it owns schema-backed names, labels, attributes, cardinality metadata, and encoders, while package-local code keeps exporter/provider setup, service identity, Restate replay gates, and runtime-specific bridges. `@overeng/oxc-config` now ships `overeng/no-raw-otel-primitives` with generated rollout config, `@overeng/utils-dev/otelite` gains reusable metric/log expectation helpers, and `restate-effect` adopts the same idiom for internal spans while preserving hook-owned Restate spans and replay-aware metrics.

- **@overeng/utils/node/otel-attrs**: Add schema-first OTEL attribute and span contracts (`OtelAttr`, `OtelAttrs`, `OtelSpan`) plus otelite expectation helpers that derive span assertions from the same compiled attribute encoders used by runtime instrumentation. Ambiguous encodings fail closed unless explicitly annotated, redacted values only support redacted/drop policies, and span definitions require the dedicated `OtelAttr.spanLabel()` contract.

- **@overeng/utils-dev/otelite**: Add an Effect-native otelite test harness and trace assertion DSL. `OteliteTestHarness` wraps scoped `Otelite.capture` lifetimes, in-process OTLP exporter wiring, serialized env-backed capture setup, flush-before-inspect helpers, and ergonomic `captureTest` / `captureInProcessTrace` / `captureEnvTrace` helpers. `expectTrace` adds runner-agnostic span assertions for service/name/attribute matching, `span.label` enforcement, and same-trace topology checks.
- **@overeng/utils-dev/otelite**: Add a vitest ↔ otelite capture bridge that wires an in-process `Otelite.capture` receiver to a vitest test's OTLP trace exporter, so spans emitted IN-PROCESS through the normal `@effect/opentelemetry` `OtlpTracer` layer land in a capture the test can assert over. `makeOteliteCaptureLayer(options?)` is a scoped `Layer` that boots ONE receiver, exposes its `CaptureHandle` via the new `OteliteCapture` `Context.Tag`, AND installs the trace exporter pointed at `${handle.endpoints.http}/v1/traces`; used with `@effect/vitest`'s `layer(...)` it gives a PER-FILE lifecycle (one receiver per test file, shared across that file's tests; tests disambiguate by a unique `service.name` / span name) — the cheap default per decision 0015, with per-test available by giving each `layer(...)` its own instance. A test does `const cap = yield* OteliteCapture; …; yield* cap.inspect({ signal: 'traces', name })`. `flushCaptureSpans()` force-flushes the exporter (the emitter's job) before inspecting. Silent-failure guard: a misrouted exporter (the `/v1/traces` suffix bug, see Fixed) lands nothing, so the demonstrator's non-zero `inspect`/`span_count` assertions FAIL the test rather than pass vacuously. Real-binary tests emit a span in-process through the REAL exporter and assert it round-trips, plus a regression that a bare un-suffixed URL captures nothing (#769, #772).
- **@overeng/notion-effect-client**: Add a real-consumer span-assertion demonstrator (D3, decision 0015) co-located in this client's own test suite (`src/test/otelite-span-shape.test.ts`). It drives the REAL instrumented query path (`NotionDatabases.query` → `executeRequest` → `Effect.withSpan('NotionHttp.POST')`) against a STUB upstream — a `HttpClient.make(...)` answering the one `POST /data_sources/{id}/query` endpoint with a canned empty paginated list + `x-ratelimit-remaining`/`x-request-id` headers — under the `@overeng/utils-dev/otelite` capture bridge, with NO secrets and NO network. It asserts the emitted span shape: exactly one `NotionHttp.POST` span carrying the templated `notion.http.route` = `/data_sources/{data_source_id}/query` + `notion.http.method`/`operation`/`status_code` (200) + `notion.rate_limit.remaining` (42); exactly one auto `http.client POST` child from `@effect/platform` whose `url.path` proves the stub served the request; a non-zero `span_count` (silent-export guard); and a public-repo leak guard that NO captured span attribute carries an `authorization` header or the token value (`@effect/platform` records only a header subset and excludes Authorization). The churn-coupled `notion.http.*` assertions sit next to the instrumentation that churns; the bridge stays a lean shared helper. The shadowing gotcha (the bridge re-exports the exporter's `FetchHttpClient` as `HttpClient.HttpClient`) is resolved by providing the stub to the effect-under-test directly (`Effect.provide`, innermost-wins) so the consumer sees the stub. Runs the real nix-built `otelite` binary on `PATH` (#769, #772).
Expand Down
4 changes: 2 additions & 2 deletions context/otel.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ mode = "auto" (default)
└── not set? → "local": starts per-project Collector/Tempo/Grafana
```

When in system mode, this module requires `OTEL_STATE_DIR`, `OTEL_EXPORTER_OTLP_ENDPOINT`, and the `otel` CLI. Shell entry fails immediately if any are missing. Dashboards are synced by invoking `otel dash sync` on shell entry, targeting `$OTEL_STATE_DIR/dashboards`.
When in system mode, this module requires `OTEL_STATE_DIR`, `OTEL_EXPORTER_OTLP_ENDPOINT`, and `OTEL_GRAFANA_URL`. Shell entry fails immediately if those required environment variables are missing. Dashboard sync is best-effort: when a compatible legacy `otel` CLI is available, shell entry invokes `otel dash sync` against `$OTEL_STATE_DIR/dashboards`; otherwise it warns and continues.

## Environment Variables

Expand Down Expand Up @@ -190,7 +190,7 @@ jsonnet -J path/to/grafonnet dt-tasks.jsonnet | jq .

### Project Dashboards (`.otel/dashboards.json`)

Projects define their own dashboards in `.otel/dashboards.json`. In system mode, dashboard syncing is delegated to `otel dash sync` on shell entry. `extraDashboards` is local-mode only and is rejected in system mode.
Projects define their own dashboards in `.otel/dashboards.json`. In system mode, dashboard syncing is best-effort on shell entry when a compatible legacy `otel` CLI is available; missing dashboard sync tooling must not block the shell because the standalone `otel` binary is retired. `extraDashboards` is local-mode only and is rejected in system mode.

## Data Storage

Expand Down
1 change: 1 addition & 0 deletions devenv.nix
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,7 @@ let
"packages/@overeng/notion-effect-schema"
"packages/@overeng/notion-md"
"packages/@overeng/notion-react"
"packages/@overeng/otel-contract"
"packages/@overeng/oxc-config"
"packages/@overeng/pty-effect"
"packages/@overeng/react-inspector"
Expand Down
28 changes: 28 additions & 0 deletions genie/oxlint-base.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,18 @@ import type {
OxlintOverride,
} from '../packages/@overeng/genie/src/runtime/mod.ts'

type OxlintRuleSeverity = 'off' | 'warn' | 'error'

type OtelOxlintRulesArgs = {
/** Severity for raw Effect/Stream OTEL span primitives. */
readonly rawOtel: OxlintRuleSeverity
/**
* Reserved for the second enforcement tier, after `OtelOperation` fully
* replaces product-code `OtelSpan.unsafe*` usage.
*/
readonly unsafeContract?: OxlintRuleSeverity
}

/** Standard ignore patterns for oxlint across all repos */
export const baseOxlintIgnorePatterns = [
'**/node_modules/**',
Expand Down Expand Up @@ -48,6 +60,17 @@ export const baseOxlintCategories = {
restriction: 'off',
} as const satisfies OxlintConfigArgs['categories']

/**
* Shared OTEL lint policy helper.
*
* Repos should use this instead of spelling raw rule names inline so the
* cross-megarepo rollout can move from warn to error without policy drift.
*/
export const otelOxlintRules = ({ rawOtel }: OtelOxlintRulesArgs): OxlintOverride['rules'] =>
({
'overeng/no-raw-otel-primitives': rawOtel,
}) satisfies OxlintOverride['rules']

/** Standard rules shared across all repos */
export const baseOxlintRules = {
// Disallow dynamic import() and require() - helps with static analysis and bundling
Expand Down Expand Up @@ -80,6 +103,9 @@ export const baseOxlintRules = {
// Enforce proper type imports
'typescript/consistent-type-imports': 'warn',

// OTEL raw primitive enforcement is enabled through generated repo overrides.
'overeng/no-raw-otel-primitives': 'off',

// Don't enforce type vs interface
'typescript/consistent-type-definitions': 'off',

Expand Down Expand Up @@ -158,6 +184,7 @@ export const generatedFilesRules = {
'overeng/exports-first': 'off',
'overeng/jsdoc-require-exports': 'off',
'overeng/named-args': 'off',
'overeng/no-raw-otel-primitives': 'off',
'unicorn/consistent-function-scoping': 'off',
} as const satisfies OxlintOverride['rules']

Expand Down Expand Up @@ -212,6 +239,7 @@ export const baseOxlintOverrides = [
files: ['**/*.test.ts', '**/*.test.tsx', '**/*.spec.ts', '**/*.spec.tsx', '**/test/**'],
rules: {
'overeng/named-args': 'off',
'overeng/no-raw-otel-primitives': 'off',
'unicorn/no-array-sort': 'off',
'unicorn/consistent-function-scoping': 'off',
'require-yield': 'off',
Expand Down
9 changes: 9 additions & 0 deletions genie/packages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,12 @@
* All internal @overeng/* package short names.
*/
export const internalPackages = [
'agent-session-ingest',
'content-address',
'effect-ai-claude-cli',
'effect-path',
'effect-react',
'effect-rpc-tanstack',
'effect-schema-form',
'effect-schema-form-aria',
'genie',
Expand All @@ -22,14 +26,19 @@ export const internalPackages = [
'notion-datasource-sync',
'notion-effect-client',
'notion-effect-schema',
'notion-md',
'notion-react',
'otel-contract',
'oxc-config',
'pty-effect',
'react-inspector',
'restate-effect',
'tui-core',
'tui-react',
'tui-stories',
'utils',
'utils-dev',
'workflow-report',
] as const

/** Short name of an internal @overeng/* package. */
Expand Down
Loading
Loading