braith 0.1.0 → 0.2.0

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.
Files changed (3) hide show
  1. package/README.md +2 -0
  2. package/dist/cli.js +138 -9
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -4,4 +4,6 @@ Run `npx braith init` in your application repository, then follow `.braith/SETUP
4
4
 
5
5
  Commands: `init`, `enroll`, `validate`, `push`, `release`, `test`.
6
6
 
7
+ Version 0.2.0 adds optional discovery context alongside configured outcomes. The coding tool installs supported capture across the app and creates a static catalogue, starting in shadow mode. Discovery does not require a declared outcome for every flow. Capture/source changes enter the local review map; source files are not uploaded. The `test` command remains an isolated configured-outcome fixture, not a discovery accuracy test. Exercise actual hooks separately with normal, failed and recovered interactions.
8
+
7
9
  Use a short-lived enrollment prompt from the Braith dashboard to get scoped setup credentials. Keep `.braith/credentials.json` private. A pushed manifest is not active until you bind it to a release or explicitly select `release default`.
package/dist/cli.js CHANGED
@@ -19860,6 +19860,99 @@ var DecisionRecordSchema = external_exports.object({
19860
19860
  }
19861
19861
  });
19862
19862
 
19863
+ // ../contracts/src/discovery.ts
19864
+ var DISCOVERY_EVENT = "braith_observation";
19865
+ var catalogue = (limit) => external_exports.record(IdentifierSchema, external_exports.string().trim().min(3).max(160)).refine(
19866
+ (value) => Object.keys(value).length <= limit,
19867
+ "Context catalogue too large"
19868
+ );
19869
+ var DiscoveryConfigSchema = external_exports.object({
19870
+ enabled: external_exports.literal(true),
19871
+ mode: external_exports.enum(["shadow", "review"]).default("shadow"),
19872
+ application: external_exports.string().trim().min(10).max(600),
19873
+ surfaces: catalogue(64).default({}),
19874
+ actions: catalogue(128).default({}),
19875
+ feedback: catalogue(64).default({})
19876
+ }).strict();
19877
+ var DiscoveryObservationSchema = external_exports.object({
19878
+ kind: external_exports.enum([
19879
+ "action",
19880
+ "navigation",
19881
+ "feedback",
19882
+ "request",
19883
+ "state",
19884
+ "error"
19885
+ ]),
19886
+ surface: IdentifierSchema,
19887
+ action: IdentifierSchema.optional(),
19888
+ feedback: IdentifierSchema.optional(),
19889
+ result: external_exports.enum([
19890
+ "started",
19891
+ "success",
19892
+ "failure",
19893
+ "pending",
19894
+ "changed",
19895
+ "unchanged",
19896
+ "denied",
19897
+ "cancelled",
19898
+ "unknown"
19899
+ ]),
19900
+ httpStatus: external_exports.number().int().min(100).max(599).optional(),
19901
+ contentKind: external_exports.enum(["html", "json", "file", "empty", "other"]).optional(),
19902
+ position: external_exports.number().int().min(0).max(31),
19903
+ end: external_exports.boolean()
19904
+ }).strict();
19905
+ var DiscoveryEvidenceSchema = DiscoveryObservationSchema.extend({
19906
+ eventId: external_exports.string().min(1).max(256),
19907
+ occurredAt: IsoDateTimeSchema,
19908
+ sourceTrust: SourceTrustSchema
19909
+ }).strict();
19910
+ var DiscoveryStateSchema = external_exports.object({
19911
+ schemaVersion: external_exports.literal(1),
19912
+ context: DiscoveryConfigSchema,
19913
+ observations: external_exports.array(DiscoveryEvidenceSchema).min(2).max(32)
19914
+ }).strict().superRefine((value, ctx) => {
19915
+ const ids = /* @__PURE__ */ new Set();
19916
+ value.observations.forEach((item, index) => {
19917
+ if (ids.has(item.eventId) || item.position !== index || item.end !== (index === value.observations.length - 1))
19918
+ ctx.addIssue({
19919
+ code: "custom",
19920
+ message: "Discovery requires a complete ordered window"
19921
+ });
19922
+ ids.add(item.eventId);
19923
+ });
19924
+ });
19925
+ var DiscoveryDecisionSchema = external_exports.object({
19926
+ assessment: external_exports.enum(["ordinary", "possible_problem", "insufficient"]),
19927
+ confidence: external_exports.number().min(0).max(1),
19928
+ problemProbability: external_exports.number().min(0).max(1),
19929
+ category: external_exports.enum([
19930
+ "contradiction",
19931
+ "friction",
19932
+ "access",
19933
+ "data_loss",
19934
+ "delivery",
19935
+ "other",
19936
+ "none"
19937
+ ]),
19938
+ severity: external_exports.enum(["low", "medium", "high", "critical"]),
19939
+ evidenceStart: external_exports.string().min(1).max(256).nullable(),
19940
+ evidenceEnd: external_exports.string().min(1).max(256).nullable()
19941
+ }).strict();
19942
+ var DiscoveryResultSchema = external_exports.discriminatedUnion("status", [
19943
+ external_exports.object({
19944
+ kind: external_exports.literal("discovery"),
19945
+ status: external_exports.literal("evaluated"),
19946
+ decision: DiscoveryDecisionSchema,
19947
+ usage: ModelUsageSchema
19948
+ }).strict(),
19949
+ external_exports.object({
19950
+ kind: external_exports.literal("discovery"),
19951
+ status: external_exports.literal("unavailable"),
19952
+ reason: external_exports.string().min(1).max(200)
19953
+ }).strict()
19954
+ ]);
19955
+
19863
19956
  // ../contracts/src/events.ts
19864
19957
  var EventNameSchema = external_exports.string().trim().min(1).max(128).regex(/^[a-z][a-z0-9_]*$/, "Event names must use lower_snake_case");
19865
19958
  var EventBaseSchema = external_exports.object({
@@ -19883,7 +19976,16 @@ function requireSubject(value, ctx) {
19883
19976
  });
19884
19977
  }
19885
19978
  }
19886
- var EventInputSchema = EventBaseSchema.superRefine(requireSubject);
19979
+ var EventInputSchema = EventBaseSchema.superRefine((event, ctx) => {
19980
+ requireSubject(event, ctx);
19981
+ if (Object.keys(event.properties).length > 24 || new TextEncoder().encode(JSON.stringify(event.properties)).byteLength > 1024 || new TextEncoder().encode(JSON.stringify(event)).byteLength > 4096) {
19982
+ ctx.addIssue({
19983
+ code: "custom",
19984
+ path: ["properties"],
19985
+ message: "Events allow at most 24 properties, 1 KiB of properties and 4 KiB total"
19986
+ });
19987
+ }
19988
+ });
19887
19989
  var AcceptedEventSchema = EventBaseSchema.extend({
19888
19990
  projectId: OpaqueIdSchema,
19889
19991
  environment: EnvironmentSchema,
@@ -20088,6 +20190,9 @@ var EventDefinitionSchema = external_exports.object({
20088
20190
  allowedProperties: external_exports.record(IdentifierSchema, PropertyTypeSchema),
20089
20191
  requiredProperties: external_exports.array(IdentifierSchema).max(24)
20090
20192
  }).superRefine((definition, ctx) => {
20193
+ if (Object.keys(definition.allowedProperties).length > 24) {
20194
+ ctx.addIssue({ code: "custom", path: ["allowedProperties"], message: "At most 24 properties are supported" });
20195
+ }
20091
20196
  for (const property of definition.requiredProperties) {
20092
20197
  if (!(property in definition.allowedProperties)) {
20093
20198
  ctx.addIssue({
@@ -20145,9 +20250,19 @@ var ProjectManifestSchema = external_exports.object({
20145
20250
  schemaVersion: external_exports.literal(1),
20146
20251
  manifestVersion: external_exports.string().trim().min(1).max(128),
20147
20252
  projectKey: external_exports.string().trim().min(1).max(256),
20148
- eventDefinitions: external_exports.array(EventDefinitionSchema).min(1).max(250),
20149
- intents: external_exports.array(IntentDefinitionSchema).min(1).max(15)
20253
+ eventDefinitions: external_exports.array(EventDefinitionSchema).max(250),
20254
+ intents: external_exports.array(IntentDefinitionSchema).max(15),
20255
+ discovery: DiscoveryConfigSchema.optional()
20150
20256
  }).superRefine((manifest, ctx) => {
20257
+ if (!manifest.discovery && (!manifest.intents.length || !manifest.eventDefinitions.length)) {
20258
+ ctx.addIssue({ code: "custom", message: "A manifest needs configured outcomes or enabled discovery" });
20259
+ }
20260
+ if (manifest.eventDefinitions.some((event) => event.name === DISCOVERY_EVENT)) {
20261
+ ctx.addIssue({ code: "custom", message: "braith_observation is reserved for validated discovery capture" });
20262
+ }
20263
+ if (new TextEncoder().encode(JSON.stringify(manifest)).byteLength > 65536) {
20264
+ ctx.addIssue({ code: "custom", message: "Manifest exceeds the 64 KiB delivery limit" });
20265
+ }
20151
20266
  const eventMap = new Map(manifest.eventDefinitions.map((event) => [event.name, event]));
20152
20267
  if (eventMap.size !== manifest.eventDefinitions.length) {
20153
20268
  ctx.addIssue({
@@ -20558,17 +20673,17 @@ function localCredentials(root) {
20558
20673
  }
20559
20674
  var setupInstructions = `# Set up Braith
20560
20675
 
20561
- Instrument the application's important user outcomes using the Braith SDK.
20676
+ Install continual customer-problem discovery and stronger checks for the application's important outcomes. Inspect the repository locally; the developer should not have to define each flow.
20562
20677
 
20563
- 1. Inspect the repository and identify 3\u20135 critical user-facing outcomes. Prefer outcomes tied to activation, payment, retained access, data creation, export, invitation, or integration setup.
20564
- 2. For each outcome, describe the observable success boundary. A UI success message is not enough when the outcome depends on committed server state.
20565
- 3. Identify meaningful entry, progress, success, contradiction, recovery, and explicit cancellation signals. For each outcome, include at least one repeatable post-success contradiction when one exists (for example, an upgrade prompt after payment, a created project returning Not Found, or an export opening empty). Jev can interpret these ambiguous signals, but the incident policy still needs them declared. Do not instrument every interaction.
20678
+ 1. Inspect the repository's routes, shared UI components, feedback and request/state boundaries. Create a static discovery catalogue in braith.config.json: enabled true, mode shadow, application purpose, surfaces, actions and feedback. These are descriptions of what the app does, not rules listing possible failures. Configure the SDK's discovery catalogue, route templates and stable action/feedback selectors. Cover supported app surfaces, including flows without configured outcomes. Never derive catalogue labels from customer content, URL parameters or form values.
20679
+ 2. Alongside broad discovery, choose a few critical outcomes for stronger explicit checks. Describe their observable success boundary. A UI success message is not enough when the outcome depends on committed server state.
20680
+ 3. Add finite observations at shared server/job/access/state boundaries where browser status alone cannot reveal the result. Use client.observeWindow on the server for a coherent same-subject sequence, or Braith.observe in the browser for a safe semantic summary. Use only catalogue identifiers and fixed kind/result/status/contentKind fields; never send values or request/response bodies. A local before/after comparison may report changed or unchanged without sending the underlying data. Automatic browser capture handles supported clicks, submits, navigation, same-origin fetch summaries and declared feedback selectors. It does not read page text, inspect network bodies, or prove that every feature is covered.
20566
20681
  4. Add allowlisted Braith events at those code locations. In browser code, use the named Braith export from @braith/sdk and call Braith.init({ projectKey, endpoint }) once, then Braith.identify and Braith.track. On the server, use createServerClient({ projectKey, endpoint }), pass userId and attemptId in track context, and flush before a short-lived process exits. Use server events for authoritative facts and propagate an attemptId across the journey.
20567
- 5. Replace the example in braith.config.json with the resulting event definitions and intents. Keep the set to 3\u20135 outcomes initially.
20682
+ 5. Additionally identify 3\u20135 critical user-facing outcomes, or fewer when appropriate, for stronger explicit checks. Define entry, authoritative success, progress, contradiction, recovery and cancellation signals. A UI success message alone cannot establish committed server state. Replace the example event definitions and intents with these real outcomes. Their absence must not exclude other activity from discovery; discovery-only apps may have empty arrays. Broad capture is opt-in, defaults to shadow review, and uses the existing event/analysis allowance: 20% of analysis capacity for discovery and 80% for core checks. Start at 10% ordinary-window sampling and at most 10 windows/hour per browser; observed failures are prioritised within that same cap. State coverage gaps and privacy choices honestly.
20568
20683
  6. Run the short-lived Braith enrollment command from the dashboard prompt. It stores keys only in .braith/credentials.json, ignored by .braith/.gitignore. For a later setup prompt, run braith enroll <token> --replace; the old local file is retained if renewal fails. Set the browser and server project keys in the corresponding application deployment environment. Never commit the server key. The temporary management key expires after 30 minutes.
20569
20684
  7. Run npx braith validate, resolve errors, and review every warning or unverifiable source check.
20570
20685
  8. Present the instrumentation and configuration diff to the developer for review. Do not upload the repository or deploy automatically.
20571
- 9. After review, run npx braith push, then npx braith test. Push records .braith/source-map.json, a local review map of intent IDs to source files and hashes; it uploads only the manifest. Commit and review that map with the instrumentation. Use npx braith test --semantic to check a configured browser-contradiction journey with Jev. Test incidents are isolated from production usage and alerts.
20686
+ 9. After review, run npx braith push. If core outcomes are configured, run npx braith test (and --semantic where supported). These isolated fixtures check configured-outcome plumbing, not discovery accuracy. Exercise real discovery hooks locally with normal, failed and recovered interactions; inspect sanitised payloads without exposing secrets. Push records .braith/source-map.json, a local review map of outcome and discovery source files and hashes; it uploads only the manifest. Commit and review that map with the instrumentation. Do not present synthetic test incidents as proof of real coverage or model accuracy.
20572
20687
  10. Before deployment, run npx braith release <your-build-id> to bind this reviewed manifest to the exact release used in Braith.init({ release }). For applications that cannot supply release metadata, explicitly run npx braith release default after review. New pushes never silently activate. A rollback binds a new build ID to the previous manifest version; do not rewrite an existing release binding.
20573
20688
  11. When a monitored workflow changes in a later release, validate again. A changed mapped source file or config requires a new manifestVersion, review of the affected code-to-outcome mapping, and another push before release binding. This hash check is only a reminder: code outside mapped files may affect outcomes, so inspect the whole workflow. Keep the previous manifest available for attempts already in progress. Never redefine success to make a regression look healthy.
20574
20689
  `;
@@ -20576,6 +20691,14 @@ var exampleConfig = {
20576
20691
  schemaVersion: 1,
20577
20692
  manifestVersion: "local-v1",
20578
20693
  projectKey: "env:BRAITH_PROJECT_KEY",
20694
+ discovery: {
20695
+ enabled: true,
20696
+ mode: "shadow",
20697
+ application: "A subscription app where customers pay for access to reporting tools.",
20698
+ surfaces: { billing: "Choose a plan and pay for access", reports: "Use reports included in the selected plan" },
20699
+ actions: { upgrade: "Upgrade the account", open_report: "Open a report" },
20700
+ feedback: { upgraded: "The app reports paid access is active", upgrade_required: "The app asks the customer to upgrade for access" }
20701
+ },
20579
20702
  eventDefinitions: [
20580
20703
  {
20581
20704
  name: "checkout_started",
@@ -20680,6 +20803,10 @@ function buildSourceMap(root, manifest) {
20680
20803
  }));
20681
20804
  const intents = {};
20682
20805
  const files = {};
20806
+ const discovery = manifest.discovery ? sources.filter(
20807
+ (source) => /\b(discovery|observeWindow|observe|catalogue)\b/.test(source.contents) && /braith/i.test(source.contents)
20808
+ ).map((source) => source.path) : void 0;
20809
+ for (const path of discovery ?? []) files[path] = sha256(sources.find((source) => source.path === path).contents);
20683
20810
  for (const intent of manifest.intents) {
20684
20811
  const names = new Set([
20685
20812
  ...intent.entrySignals,
@@ -20700,6 +20827,7 @@ function buildSourceMap(root, manifest) {
20700
20827
  manifestVersion: manifest.manifestVersion,
20701
20828
  configHash: sha256(readFileSync(join(root, CONFIG_NAME), "utf8")),
20702
20829
  intents,
20830
+ ...discovery ? { discovery } : {},
20703
20831
  files
20704
20832
  };
20705
20833
  }
@@ -20770,6 +20898,7 @@ function validateProject(root = process.cwd()) {
20770
20898
  if (!/(createBrowserClient|createServerClient|Braith\.init)\s*\(/.test(source)) {
20771
20899
  messages.push({ level: "warning", message: "Braith SDK initialization was not found in source" });
20772
20900
  }
20901
+ if (manifest.discovery && !/\b(discovery|observeWindow)\b/.test(source)) messages.push({ level: "unable", message: "Discovery is enabled but no capture configuration or server observation reference was found. This scan does not prove coverage." });
20773
20902
  for (const event of manifest.eventDefinitions) {
20774
20903
  if (!source.includes(event.name)) {
20775
20904
  messages.push({ level: "unable", message: `${event.name}: no source reference was found; wrappers or generated code may hide it` });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "braith",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "description": "Braith coding-agent setup and outcome manifest CLI",
5
5
  "license": "UNLICENSED",
6
6
  "publishConfig": { "access": "public" },