Skip to content

feat!: start profile in every SDK, the Dart SDK, and control point on the wire - #18

Merged
battuashwik merged 43 commits into
mainfrom
feat/server-sdk-start-profile
Oct 9, 2026
Merged

battuashwik merged 43 commits into
mainfrom
feat/server-sdk-start-profile

Conversation

@battuashwik

@battuashwik battuashwik commented Oct 9, 2026 •

Copy link
Copy Markdown
Member

Brings feat/server-sdk-start-profile into main: 42 commits, nothing behind main.

What changes

  • Start profile in all eight SDKs (ADR-0012): a one-line setup for each language. Node @fireweaveai/server-sdk/start, web /start with /vite and /define build helpers, Python fireweave.start, Go package fw, Java ai.fireweave.sdk.start, Rust fireweave::start, Dart client.dart / server.dart, Swift FireweaveStart.
  • Dart SDK (ADR-0011): control points and target registration for Flutter and the Dart VM, registered in the conformance matrix, CI and release.
  • Shared start-profile spec and suite: spec/start-profile.md and contracts/start, run in every SDK.
  • Hardening across SDKs:
    • last good decisions are kept on a failed re-fetch;
    • redaction follows one shared contract;
    • key failures are reported;
    • Python refuses redirects;
    • Go and Node remote-adapter races are fixed.
  • Breaking: control point on the wire (ADR-0013, 56fc2c5, 7e248c9). The SDKs and the wire envelopes say "control point" instead of "flag".
  • CI and release: Java staging builds, the Swift mirror push, Dart staging releases, the Deno check, clippy at MSRV 1.85, and fixes for five jobs that also fail on master.
  • Docs: README redesign, removal of out-of-date docs, and the ADR-0011/0012/0013 and changelog updates.
  • ea3dc99: syncs sdks/python/uv.lock with the 2.2.0 package version.

Releasing 3.0.0

This PR does not change version numbers; every manifest stays at 2.2.0. After merging, run release.yml with component: all, bump: major, channel: production. tools/release/version.sh computes 3.0.0 from each manifest (Go and Swift: from their tags) and stamps the manifests and build info.

  • Go module path: 82dad10 moves the Go module to github.com/FireWeave-HQ/fireweave-sdk/sdks/go/v3. Go refuses a v3 tag on a /v2 module, which is how sdks/go/v2.2.0 shipped unusable. Go users change their import path from …/sdks/go/v2 to …/sdks/go/v3.

Before merging

  • ADR-0013 changes the wire format. SDKs built from this branch need a FireWeave server that speaks the control-point wire. That server is the fireweaveai-platform feature/integration-refactor release, not yet in production. Merge and publish in that order.
  • PR feat: start profile for all eight SDKs, a one-line setup (review only) #16 (draft, from this branch into feature/new-development) was opened for review only. Close it once this PR is accepted.
  • Device check: Swift passes CI on Swift 6.0.3 and 6.2.1 but has not been run on a device.

Checked

  • Python SDK tests pass locally (uv run --extra dev pytest).
  • uv lock --check passes for sdks/python.
  • Go: go build, go vet and go test ./... pass for sdks/go on /v3, gofmt is clean, and examples/go builds.
  • Other SDKs: see this PR's CI.

battuashwik and others added 30 commits September 2, 2026 14:07
… for Flutter and the Dart VM

Eighth conformant SDK, published on pub.dev as `fireweave` (ADR-0011). One package for
Flutter on Android, iOS, macOS, Windows, Linux, and web, for the Dart VM (servers, CLIs),
and for Dart compiled to JavaScript or WebAssembly; pubspec declares all six platforms.

Shape follows web/swift (ADR-0009): initFireweave/refresh/identify prefetch a decision
cache asynchronously and the nine controlPoints methods are synchronous cache reads, so
they are safe inside a widget's build(). Both modes per spec/modes.md; local registerTarget
is recorded in-process and traced to an injectable sink; a boot that misses the ceiling
enters STALE rather than READY.

Zero runtime dependencies and no package:flutter dependency: the HTTP transport is chosen
by conditional export from SDK libraries alone — dart:io on the VM and Flutter
mobile/desktop, the browser's fetch hand-bound with dart:js_interop on the web (valid under
dart compile wasm). Guard tests pin the dependency budget, the three-layer import
boundaries, surface parity with conformance/surface, dart:io/dart:js_interop confinement,
no environment reads, and no vendor key shapes.

Conformance: a real runner over the shared 65 fixtures with swift's disposition —
37 pass, 15 skipped-with-documented-limitation, 13 skipped-v1-out-of-scope — pinned by a
dart test wrapper.

Verified on Dart 3.13.3 and the 3.8.3 floor: format, analyze --fatal-infos, 120 VM tests,
103 tests under headless Chromium (dart2js + fetch transport), dart compile js/wasm of the
example, the compiled JS run under Node fetch against test-server, and
dart pub publish --dry-run with 0 warnings.

Signed-off-by: Ashwik Battu <battuashwik@gmail.com>
…I, release, docs

Everywhere swift is registered, dart is now too:

- conformance/surface: dart casing + green cell; contracts/harness.md: runner row, 65x8.
- tools/conformance/compare.mjs: dart joins the real-no-baseline tier (rust, swift);
  scripts/conformance-all.sh runs the dart runner and feeds the comparator.
- tools/release/version.sh + version.test.sh + changelog.sh: component `dart`, manifest
  sdks/dart/pubspec.yaml, tag dart/vX.Y.Z, staging N read from pub.dev.
- ci.yml: dart job on the 3.8 floor and stable — format, analyze, VM tests, Chrome tests,
  dart compile js/wasm of the example, conformance, publish dry run; differential job sets
  up Dart. release.yml: dart component; staging = dart pub publish --dry-run + tag (pub.dev
  has no staging registry), production = pub.dev automated publishing (OIDC) on the
  release environment — a scope extension flagged for human sign-off in RELEASE.md, not a
  separate authorization. security.yml: osv-scan over a generated pubspec.lock.
- CODEOWNERS, README, CHANGELOG, .gitignore (pubspec.lock deliberately uncommitted so the
  CI matrix resolves per leg).

Verified: tools/release/version.test.sh 69 passed; all three workflows parse; the
comparator reports dart 37/0/15/13 with no divergences in a 65x8 run.

Signed-off-by: Ashwik Battu <battuashwik@gmail.com>
So release.yml patch+staging computes 0.0.1-staging.1 (release.yml has no free-text version input).

Co-authored-by: Cursor <cursoragent@cursor.com>
dart pub publish --dry-run exited 65 because apply dirties pubspec.yaml and
CHANGELOG omits -staging.N; use --force (same idea as cargo --allow-dirty).
Read dart staging-N from git tags, since tags are the staging artifact.

Co-authored-by: Cursor <cursoragent@cursor.com>
dart pub rejects combining --force with --dry-run; --ignore-warnings is
the flag that keeps apply-dirtied pubspec / missing staging CHANGELOG
from exiting 65.

Co-authored-by: Cursor <cursoragent@cursor.com>
release.yml has no free-text version; minor+staging from 2.2.0 yields 2.3.0-staging.N.

Co-authored-by: Cursor <cursoragent@cursor.com>
…guage quick starts

- Light/dark Fireweave banner (.github/assets) via <picture>, linked to
  app.fireweave.ai.
- Live release pills per SDK (npm, PyPI, Go proxy, Maven Central,
  crates.io; Swift builds from source).
- A short intro, then one collapsible quick start per language. Every
  snippet was compiled and run against the published 2.3.0 package and
  the repo's protocol stub; Swift was checked statically.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Signed-off-by: Ashwik Battu <battuashwik@gmail.com>
Deleted (no longer true of the v1 control-points SDKs, or completed
working documents):
- docs: quickstart, architecture, compatibility, extensions, lifecycle,
  openfeature, posthog, migration (pre-v1 OpenFeature/PostHog era).
- IMPLEMENTATION-PLAN.md, docs/java-parity-plan.md, docs/orchestration,
  docs/research, docs/reviews, docs/security (plans and reports for
  shipped or superseded work).
- examples/python/fastapi_app.py and its test (fail to import on 2.3.0).
- test-server README, PLAN and PATHS (describe routes and fault triggers
  the stub does not implement).

Trimmed in place:
- Remaining docs, SDK READMEs, contracts and spec: drop OpenFeature,
  vendor-adapter, extension-namespace and "unpublished" claims. The Java
  README regains current Maven coordinates, privacy.md keeps its true PII
  guidance and the Node exposure disclosure, and troubleshooting keeps the
  NotReady diagnosis.
- CONTRIBUTING, GOVERNANCE, SUPPORT, SECURITY, CODEOWNERS, RELEASE.md and
  issue/PR templates: drop the four-language framing and completed setup
  steps; templates list all seven SDKs; master -> main where the branch
  is named.
- CHANGELOG and ADR-0006 keep their wording; links to the removed guides
  now point at a permanent commit URL.
- Code and workflows: comment and message text only; no logic changed.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Signed-off-by: Ashwik Battu <battuashwik@gmail.com>
…-DPI screens

Both banners exported from Figma at 4x (6336x1584, was 1584x396);
paths are unchanged, so README.md needs no edit.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Signed-off-by: Ashwik Battu <battuashwik@gmail.com>
shutdown() raced flush() against a setTimeout and never cleared the
timer when the flush won, so a clean shutdown still held the process
open for shutdownTimeoutMs (10 s by default). The timer is now cleared
in a finally block.

The regression test runs a child process that starts a remote client
and shuts it down; it took 10,027 ms to exit before the fix.

Signed-off-by: Ashwik Battu <battuashwik@gmail.com>
Adds @fireweaveai/server-sdk/start and /register so a server app can
start FireWeave with one import instead of a generated harness:

  import { start } from '@fireweaveai/server-sdk/start';
  import { flags } from './flags';
  start({ flags });

The start profile is a layer over the unchanged core (ADR-0011). Only
it reads the environment:

- mode: the `mode` option wins; otherwise a key means remote, and no
  key means local only when the environment name (`environment`
  option, FIREWEAVE_ENV, APP_ENV, NODE_ENV) is a dev name. Anything
  else fails closed with a Configuration error naming the variable.
- endpoint: defaults from the SDK build's channel, which
  version.sh apply server now stamps into src/start/build-info.ts
  (-staging.N builds call staging, others production). `url` or
  FIREWEAVE_URL overrides it; the allowlist follows the chosen host.
- key: `key` option, then FIREWEAVE_KEY, then FW_PROJECT_API_KEY with
  one warning. Browser keys, analytics-vendor keys and org/CLI tokens
  are rejected; messages name the source, never the value.
- flags: defineFlags() in its own file, passed as start({ flags });
  values are served in local mode only, and a read of a key missing
  from the object warns once.

`fw` exposes the 9 controlPoints methods plus identify, instanceKey,
status, client and shutdown. Reads never throw. A second identical
start() is a no-op and a conflicting one throws. A read before start()
schedules an env-only start on the next macrotask, which the
entrypoint's own synchronous start() always beats.

Guards: runtime globals and process.env are confined to start/env.ts
and start/instance.ts, start/ may import only the core barrel, the
core may never import start/, and the exports pin now lists ./start
and ./register.

Signed-off-by: Ashwik Battu <battuashwik@gmail.com>
Brings the start profile (ADR-0011) to @fireweaveai/web-sdk, with the
same options as the server SDK, so a browser app replaces its generated
harness with a flags file, one start() call and one build plugin:

  // vite.config.ts
  plugins: [fireweave()]
  // src/fireweave/start.ts
  export const ready = start({ flags });

- /start: start(), fw and defineFlags. Options: flags, mode,
  environment, url, key, plus persistence and deviceId for consent and
  analytics joins. fw adds identify, reset, deviceId, setPersistence,
  forget, ready, status and subscribe. Reads stay synchronous.
- The browser reads no environment. The key, endpoint and environment
  name are read at build time by /vite (the fireweave() plugin) or
  /define (fireweaveDefine, assertFireweaveBuild, no vite import), which
  share the browser's policy module and inject the inputs as
  __FIREWEAVE_WEB_CONFIG__. Explicit start() options still win.
- Mode rule as on the server; a build never infers local from Vite's
  --mode, only the dev server and Vitest do. Only browser keys
  (fw_public_) are accepted; a server key fails the build with a revoke
  instruction, and a Vite client build containing one fails.
- Endpoint defaults from the SDK channel: version.sh apply web now
  stamps src/start/build-info.ts like the server SDK.
- start() never throws or rejects, so it cannot blank a page: a fault
  logs once and sets state FAILED with a problem. 401/403 and
  unreachable fw-server keep their cause in status().problem.
- The device id keeps the scaffold's storage key and dev_ prefix, so
  migrated apps keep their ramp buckets. Remote mode without a DOM
  (SSR) does nothing; local mode serves the flags file everywhere.

Guards: start/ imports only the core barrel, the core never imports
start/, node/ imports only node: built-ins, its own files and the three
pure start modules, the injected config is read only behind a typeof
guard, and the exports are pinned. vite is an optional peer.

Deferred from the web plan: the top-level-await boot redirect, CSP
detection in the SDK, and the Vite 5-8 integration matrix.

Signed-off-by: Ashwik Battu <battuashwik@gmail.com>
Brings the start profile (ADR-0011) to the Python SDK with the same
rules as the server SDK:

  from fireweave.start import start, fw, define_flags
  start(flags=flags)
  fw.control_points.get_boolean_value("new-checkout", False, EvaluationContext(user_id))

- Keyword-only options flags, mode, environment, url, key, instance_id,
  env and log. Each resolves option, then FIREWEAVE_*, then the legacy
  FW_* name with one warning, then the default; blank means unset.
- Mode rule, key family checks and fail-closed ConfigurationError as on
  the server; messages name the source, never the value.
- The default endpoint follows the installed distribution's version: a
  PEP 440 prerelease (staging builds are X.Y.ZaN) calls staging, any
  other version production. No release stamping is needed.
- fw.control_points has exactly the core's nine methods; identify,
  instance_key, status, client and shutdown never raise.
- A read before start() starts from the environment at once; the first
  explicit start() replaces it once, with a warning. A forked child
  rebuilds its client from the stored config.

The core package is unchanged and still reads no environment. New AST
guards: the core never imports fireweave.start, start/ uses only names
in fireweave.__all__ and the stdlib, os.environ and the host name are
read only in start/_env.py, and importing fireweave.start reads no env.

Signed-off-by: Ashwik Battu <battuashwik@gmail.com>
Brings the start profile (ADR-0011) to the Go SDK as package
github.com/FireWeave-HQ/fireweave-sdk/sdks/go/v2/fw:

  if err := fw.Start(fw.Options{Flags: appfw.Flags}); err != nil { log.Fatal(err) }
  fw.ControlPoints().GetBooleanValue("new-checkout", false, fw.For(user.ID))

- Options Flags, Mode, Environment, URL, Key, InstanceID, Env and Log,
  with the same precedence, mode rule and key family checks as the
  server SDK. Errors are *fireweave.Error and never print a key.
- The default endpoint follows this module's version in the binary's
  build info: vX.Y.Z-staging.N calls staging, anything else, including a
  local checkout, production. No release stamping is needed.
- Client() is one permanent *fireweave.Client for the process, on a
  forwarding adapter, so a pointer captured before Start keeps working
  across Start, Shutdown and a restart. A read before Start starts from
  the environment once; Start is synchronous and does no network I/O.
- Identify, InstanceKey, Status, Shutdown, For, SDKVersion, SDKChannel.

The only core change re-exports fireweave.ValidateControlPointKey, so
flags are checked with the core's own rule. New guards: env and
host-name reads only in fw/env.go, fw imports only the fireweave facade
and the standard library, and no other package imports fw.

Signed-off-by: Ashwik Battu <battuashwik@gmail.com>
…ngelog

ADR-0011 now covers the four SDKs on this branch and explains how
Python and Go take their release channel from the version they already
carry instead of a stamped file, how their reads before start() behave,
and which core fixes from the build plan are deferred.

Signed-off-by: Ashwik Battu <battuashwik@gmail.com>
Brings the start profile (ADR-0011) to the Java SDK as the Fw facade:

  Fw.start(StartOptions.builder().flags(FireweaveFlags.FLAGS).build());
  Fw.controlPoints().getBooleanValue("new-checkout", false, ctx);

- StartOptions.builder(): flags, mode, environment, url, key,
  instanceId, env and log, with the same precedence, mode rule and key
  family checks as the server SDK. Errors are Configuration
  FireweaveExceptions that name the source, never the value.
- The default endpoint follows this artifact's version, read from a
  Maven-filtered build.properties (filtering is enabled for that one
  resource only): X.Y.Z-staging.N calls staging, anything else
  production.
- Fw.client() is one permanent FireweaveClient on a forwarding adapter,
  so a reference captured before start keeps working. A read before
  start starts from the environment once; start does no network I/O.
- identify, instanceKey (the same FNV-1a-64 host hash as Node and Go),
  status (never the key), shutdown, sdkVersion, sdkChannel.

The core is unchanged. ArchitectureLayersGuardTest now admits start as
the one package beside the three layers (ADR-0011), and the new
StartConfinementGuardTest keeps env and host-name reads in StartEnv,
keeps start on the public application and domain types, and keeps every
core package from importing it.

Signed-off-by: Ashwik Battu <battuashwik@gmail.com>
Brings the start profile (ADR-0011) to the Rust SDK:

  fireweave::start::start(StartOptions { flags: fireweave_flags::flags(), ..Default::default() })?;
  fireweave::start::control_points().get_boolean_value("new-checkout", false, Some(&ctx));

- StartOptions fields flags, mode, environment, url, key, instance_id,
  env and log, with the same precedence, mode rule and key family
  checks as the server SDK. Errors are the core FireweaveError
  (Configuration, init-fatal) and never contain a key.
- The default endpoint follows CARGO_PKG_VERSION, compiled into the
  consumer's build: X.Y.Z-staging.N calls staging, anything else
  production.
- client() is one permanent FireweaveClient on a forwarding adapter. A
  read before start starts from the environment once; start does no
  network I/O. identify, instance_key (the FNV-1a-64 host hash shared
  with Node, Go and Java), status, shutdown, sdk_version, sdk_channel.
- No new dependencies; Cargo.toml and Cargo.lock are unchanged.

The only core change is `pub mod start;` in lib.rs. New guards: env and
host-name reads only in start/env.rs, start uses only crate-root
re-exports and std, and no core module references start.

Signed-off-by: Ashwik Battu <battuashwik@gmail.com>
…ngelog

ADR-0011 now covers six SDKs. It records how Java and Rust take their
channel from their own version, admits Java's start package to the
pinned package list, and notes the deferred items, including the Rust
MSRV mismatch that already fails CI's 1.75 job on master.

Signed-off-by: Ashwik Battu <battuashwik@gmail.com>
Brings sdks/dart, its CI and release wiring, and ADR-0011 (Dart control
points) onto this branch so the Dart start profile can build on it.

Conflicts, all documentation:
- README.md: kept this branch's redesigned README and added a Dart badge
  and a Dart quick-start block in its per-language style.
- .github/RELEASE.md: kept this branch's production npm jobs and trimmed
  provisioning list, and added the pub.dev jobs and its setup step.
- contracts/harness.md: kept this branch's Rust and Swift runner rows
  and added the Dart row.

Signed-off-by: Ashwik Battu <battuashwik@gmail.com>
The Dart SDK merged in with ADR-0011 (Dart control points), which is
older, already on two open PRs and cited throughout sdks/dart. The
start-profile ADR becomes ADR-0012, and every reference to it (code
comments, READMEs, guard tests, the release stamp template and the
changelog) moves with it. No behaviour changes.

Signed-off-by: Ashwik Battu <battuashwik@gmail.com>
Brings the start profile (ADR-0012) to the Dart SDK in two profiles:

  await Fireweave.start(flags: flags); // before runApp, or before serving
  fw.controlPoints.getBooleanValue('new-checkout', false);

- client.dart (Flutter, Dart web): options, then the compile-time
  defines FIREWEAVE_BROWSER_KEY, FIREWEAVE_URL and FIREWEAVE_ENV, read
  only as const literals. Browser keys only. Never throws: a fault
  sets status failed with a problem and reads serve defaults, like the
  web profile. identify, reset, deviceId, a DeviceIdStore hook, changes.
- server.dart (Dart VM): options, then FIREWEAVE_* from the process
  environment, legacy FW_* with one warning. Project keys only; throws a
  Configuration error on a fault. identify and instanceKey (the FNV-1a
  host hash shared with the other SDKs).
- Default endpoint from lib/src/start/build_info.dart (stamped on
  release); one client per isolate; the start layer owns and closes its
  own dart:io transport and logs each remote error kind once.

No new dependencies and no core change. New start_guard_test pins
dart:io, Platform.environment and const define reads to named seams;
the core portability rules keep their strength and the carve-out fails
if it goes stale. 261 tests pass on Dart 3.13.3 and 3.8.3 (120 before).

Signed-off-by: Ashwik Battu <battuashwik@gmail.com>
…ompiled)

Brings the start profile (ADR-0012) to the Swift SDK as a second
library product that re-exports the core:

  init() { try! startFireweave(flags: appFlags) }
  fw.controlPoints.getBooleanValue("new-checkout", default: false)

- startFireweave is synchronous, throws a configuration error before
  any I/O, and starts the first prefetch in the background; fw is one
  process-wide handle.
- App profile: Info.plist FIREWEAVE_BROWSER_KEY, FIREWEAVE_URL and
  FIREWEAVE_ENV; browser keys only; a dev_<UUID> device id in
  UserDefaults with persistence, setPersistence and forget for consent.
  A debug build with no key and no environment name is development
  through the target's own debug define.
- Server profile: FIREWEAVE_* from the environment, legacy names with a
  warning, project keys, instanceKey.
- Default endpoint from Sources/FireweaveStart/BuildInfo.swift.

The core target is unchanged. This was written without a Swift
toolchain and has not been compiled or run; Swift testing is being set
up separately.

Signed-off-by: Ashwik Battu <battuashwik@gmail.com>
version.sh apply dart now also writes lib/src/start/build_info.dart,
and apply swift, which had nothing to write, now writes
Sources/FireweaveStart/BuildInfo.swift in swift-format's 2-space style.
A -staging.N version stamps the staging channel. A Swift release must
commit the stamp before tagging, because SwiftPM builds the tagged
source.

Signed-off-by: Ashwik Battu <battuashwik@gmail.com>
…angelog

ADR-0012 now covers all eight SDKs, including the client and server
profiles Dart and Swift need, and notes that the Swift start profile
has not been compiled yet.

Signed-off-by: Ashwik Battu <battuashwik@gmail.com>
CI's first compile of FireweaveStart passed on Swift 6.0.3 and 6.2.1,
format lint included, with 168 Swift tests passing (85 on master). The
changelog and ADR-0012 no longer call it uncompiled; on-device checks
remain open.

Signed-off-by: Ashwik Battu <battuashwik@gmail.com>
spec/start-profile.md states the start profile's rules (SP-1..SP-26)
for every SDK: the server and client profiles, the names each reads,
option-then-variable-then-legacy precedence with blank values unset,
the fail-closed mode rule, the release-channel endpoint and allowlist,
key families per profile, flags, idempotency and reads, and the
instance key with FNV-1a 64 test vectors. spec/modes.md now scopes
"reads no environment" to the core and points at it.

contracts/start/ holds 14 fixtures (101 cases) under a closed schema,
outside the frozen 65 like contracts/web/. Cases drive each SDK's pure
resolution with injected env, build values and host names, and assert
outcomes and the variable names messages carry, never message text.
tools/conformance/compare-start.mjs validates the fixtures (zero-dep
schema subset plus cross-field checks) and aggregates per-language
reports; CI runs it in the comparator job.

Signed-off-by: Ashwik Battu <battuashwik@gmail.com>
Each SDK's own test command now runs contracts/start against its start
layer and writes compatibility-report.start.<lang>.json (gitignored):

- node, web: test/unit/start-contracts.test.ts (node is the reference)
- python: tests/test_start_contracts.py
- go: fw/start_contracts_test.go (internal test on the pure resolver)
- java: StartContractsTest in ai.fireweave.sdk.start
- rust: tests/start_contracts.rs, through three doc(hidden) test hooks
  over the crate-private resolver (start_guards bans file and JSON use
  under src/start, so the runner cannot live there)
- dart: test/start_contracts_test.dart for both profiles; the server
  and client resolution moved into pure functions (resolveServerStart,
  resolveClientStart, deriveServerInstanceKey) that start now calls,
  with no change in behaviour
- swift: StartContractsTests.swift (checked on CI)

Every fixture declared pass passes; no SDK disagreed with the spec and
no compatibility cell was changed. Each runner was shown to fail on a
planted wrong expectation.

Signed-off-by: Ashwik Battu <battuashwik@gmail.com>
- python (3.10): test_architecture_layers imported tomllib, which is
  3.11+, so collection failed on the supported floor. It now falls back
  to a narrow read of [project].dependencies when tomllib is missing.
- node (Node 20): `test:compat` ran test/compat/*.compat.test.ts, files
  that were never committed (the script arrived in f470368 without
  them). Node 24 tolerated the empty glob; Node 20 does not. The dead
  step is removed from `npm test`.
- deno (canary): newer Deno applies sdks/node/tsconfig.json (rootDir:
  src) and rejects the dist/ files the job type-checks (TS6059). The
  check now runs on a copy of dist/ outside the package.
- actionlint: SC2038 (find | xargs on swift files, now -print0 / -0)
  and SC2153 (COMPONENTS read as a misspelling of COMPONENT, renamed).
- rust (1.75.0): the locked ureq 3.4 / zeroize 1.9 graph needs Rust
  1.85, so rust-version and the CI floor move to 1.85.

Signed-off-by: Ashwik Battu <battuashwik@gmail.com>
…SRV 1.85

- deno (canary) finds sdks/node/tsconfig.json from the working
  directory, not from the checked files, so copying dist/ alone was not
  enough. The check now runs from inside the copy.
- With rust-version at 1.85, clippy's unnecessary_map_or fires on the
  start-contracts test (is_none_or is stable since 1.82).

Signed-off-by: Ashwik Battu <battuashwik@gmail.com>
…ntract, SP-23 and SP-27

spec/control-points.md: prefetching SDKs keep the last good decisions after
a failed or timed-out re-fetch and serve them as STALE; only a fetch with no
earlier success falls back to defaults with ERROR.

contracts/errors.json gains rules.redaction with 16 vectors every SDK's
redactor must pass (bearer, URL userinfo, named-assignment values and
key-shaped values become [REDACTED]; variable names stay readable), declared
in spec/errors.schema.json and contracts/errors.md rule 2.

spec/start-profile.md: client profiles never crash the app on a bad
configuration (SP-23, Swift apps included); server profiles report a
refused, rate-limited or unreachable fw-server once per kind with
lastErrorKind (SP-27).

Signed-off-by: Ashwik Battu <battuashwik@gmail.com>
…t by contract

- Resolve/RegisterTarget read closed/ready while Close wrote them with no
  synchronisation (a -race failure); every access now holds the adapter's
  mutex. The adapter owns a transport cloned from the defaults and closes
  its idle connections on Close.
- Redact implements contracts/errors.json rules.redaction (parity test over
  all 16 vectors); the bare name FW_PROJECT_API_KEY is no longer blanked, so
  start errors name it directly. Placeholder is now [REDACTED].
- fw: one Warn line per remote failure kind for the process, naming the key
  source and host, never the key; Status().LastErrorKind (SP-27).

Signed-off-by: Ashwik Battu <battuashwik@gmail.com>
…rt key failures

- The remote adapter's urllib opener re-sent Authorization to a redirect
  target; redirects are now refused (BackendUnavailable), proxies still come
  from the environment.
- Non-EvaluationContext contexts, non-str targeting keys and non-JSON
  registration properties degrade to InvalidContext instead of raising.
- The redactor implements contracts/errors.json rules.redaction.
- start: the provisional client replaced by the first explicit start() is
  shut down after the swap (reads racing it retry once); remote failures
  log once per kind with status().last_error_kind (SP-27).

Signed-off-by: Ashwik Battu <battuashwik@gmail.com>
… profile

- redactSecrets implements contracts/errors.json rules.redaction (parity
  test over all 16 vectors).
- The start profile wraps the transport it hands the core and logs one line
  per remote failure kind (key rejected, rate limited, unreachable), naming
  the key's source and the host, never the key; status().lastErrorKind
  (SP-27). The core is unchanged.

Signed-off-by: Ashwik Battu <battuashwik@gmail.com>
The runtime already kept the cache on a failed re-fetch but reported the
backend's original reason. A STALE runtime now says STALE, as the amended
spec/control-points.md requires.

Signed-off-by: Ashwik Battu <battuashwik@gmail.com>
…fy()

- Redaction.sanitize (Java) and redact_secrets (Rust) implement
  contracts/errors.json rules.redaction, with parity tests.
- Both start profiles observe failures through their forwarding adapters
  and log one line per kind, with lastErrorKind / last_error_kind (SP-27).
- Java: Fw.verify(), one evaluation round trip that reports whether
  fw-server accepts the configured key and never throws.

Signed-off-by: Ashwik Battu <battuashwik@gmail.com>
…ver decisions

- The runtime keeps the cache and goes STALE when a re-fetch fails after a
  success, and a refresh in flight at shutdown leaves the runtime closed.
- redactSecrets implements contracts/errors.json rules.redaction.
- server.dart re-fetches every 30 s (refreshInterval; zero disables),
  swapping on success; fw.shutdown() cancels it. A remote-mode CLI must call
  fw.shutdown() to exit promptly.

Signed-off-by: Ashwik Battu <battuashwik@gmail.com>
…ers, redact by contract

- App profile: startFireweave(flags:) no longer throws; a configuration
  fault sets status .failed and reads serve defaults (SP-23). Servers stop
  the process (fatalError) from that form or throw from
  try startFireweave(FireweaveStartOptions(...)).
- Core: a failed re-fetch after a success keeps the cache as STALE; late
  results after shutdown are discarded.
- Server profile re-fetches every 30 s (refreshInterval), and logs one line
  per remote failure kind with lastErrorKind (SP-27).
- Errors.swift redacts by contracts/errors.json rules.redaction.
Written without a local toolchain; checked by CI.

Signed-off-by: Ashwik Battu <battuashwik@gmail.com>
- publish-maven publishes staging runs as X.Y.Z-staging.N to Maven Central
  (previously uploaded unpublished), refusing a staging version without
  -staging. (decision D4).
- publish-swift-mirror copies sdks/swift with its stamped BuildInfo.swift to
  the mirror repository's root and tags plain semver there, so SwiftPM can
  resolve a release (decision D5). Fails closed until the mirror and its
  SWIFT_MIRROR_DEPLOY_KEY are provisioned; RELEASE.md lists the steps.

Signed-off-by: Ashwik Battu <battuashwik@gmail.com>
…0007's guard

ADR-0007 cited v2 compatibility files that ADR-0010 deliberately retired;
the flags alias is pinned by test/unit/control-points-surface.test.ts. Also
corrects f7cc89f's message: test:compat was not pointing at files that were
never committed, but at guards ADR-0010 deleted; removing the dead script
was right for that reason.

Signed-off-by: Ashwik Battu <battuashwik@gmail.com>
ADR-0013 supersedes ADR-0007's flag boundaries and its flags alias,
and ADR-0010's restatement, for 3.0.0: POST /v1/control-points/evaluate
with controlPointKeys; controlPointKey and controlPointMetadata in the
decision, capture and signal envelopes; the fireweave.controlPointVersion
and fireweave.vendorControlPointId metadata keys; ControlPointNotFound;
features.flags and the flags alias removed; the start profile's
controlPoints option. OpenFeature's FLAG_NOT_FOUND and PostHog's own
names stay. fw-server keeps the 2.x aliases until no supported SDK
calls them. The SDKs, fixtures and test server follow in one change.

Signed-off-by: Ashwik Battu <battuashwik@gmail.com>
…0013)

All eight SDKs, the fixtures and the test server move to the names
ADR-0013 fixes for 3.0.0, in one change as GOVERNANCE requires:

- evaluate calls POST /v1/control-points/evaluate with controlPointKeys;
  decisions carry controlPointKey and controlPointMetadata; node and web
  capture and signal events carry controlPointKey
- metadata keys fireweave.controlPointVersion and
  fireweave.vendorControlPointId
- the error kind FlagNotFound is ControlPointNotFound in every SDK;
  OpenFeature's FLAG_NOT_FOUND code stays
- the public Decision says controlPointKey / controlPointMetadata
- client.flags is removed everywhere; the surface descriptor records
  removedAlias and each SDK asserts the member is gone
- the start profile (ADR-0012, unreleased) takes controlPoints:
  defineControlPoints, local control-point types, controlPointCount,
  invalid-control-points
- fixtures use given.controlPoints, when.controlPointKey and
  expect.controlPointMetadata; the test server serves only the new route;
  the CI protocol smoke calls it

PostHog's own vocabulary (/flags, feature_flags) is unchanged. Swift is
changed by review only until CI runs it (no toolchain here).

BREAKING CHANGE: every public rename above; release all eight as 3.0.0.

Signed-off-by: Ashwik Battu <battuashwik@gmail.com>
Go requires a module at major 3 to live under a /v3 path. The release
workflow computes 3.0.0 from the manifests with bump=major, and a
sdks/go/v3.0.0 tag on a /v2 module would be refused by the Go proxy, as
sdks/go/v2.2.0 was. Imports, the example module, docs, the license check
and the Cursor rule move with it.
@battuashwik
battuashwik merged commit 45d8ae9 into main Oct 9, 2026
46 of 58 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant