@cavuno/board 2.0.0 → 2.0.1

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 (84) hide show
  1. package/CHANGELOG.md +22 -0
  2. package/CONTRIBUTING.md +25 -0
  3. package/LICENSE +21 -0
  4. package/README.md +12 -7
  5. package/SECURITY.md +28 -0
  6. package/TRADEMARKS.md +16 -0
  7. package/dist/_spec-sW85fuvT.d.mts +2731 -0
  8. package/dist/_spec-sW85fuvT.d.ts +2731 -0
  9. package/dist/bin.mjs +16 -348
  10. package/dist/{board-B8tJGX4i.d.ts → board-DKXyxwNN.d.ts} +2 -2
  11. package/dist/{board-DmRuCA8m.d.mts → board-QFeHT4UN.d.mts} +2 -2
  12. package/dist/doctor.d.mts +6 -19
  13. package/dist/doctor.d.ts +6 -19
  14. package/dist/doctor.js +16 -344
  15. package/dist/doctor.mjs +15 -343
  16. package/dist/filters.d.mts +5 -5
  17. package/dist/filters.d.ts +5 -5
  18. package/dist/format.d.mts +17 -19
  19. package/dist/format.d.ts +17 -19
  20. package/dist/go.d.mts +2 -2
  21. package/dist/go.d.ts +2 -2
  22. package/dist/go.js +1 -1
  23. package/dist/go.mjs +1 -1
  24. package/dist/index.d.mts +16 -18
  25. package/dist/index.d.ts +16 -18
  26. package/dist/index.js +39 -28
  27. package/dist/index.mjs +39 -28
  28. package/dist/{jobs-BEYJY97F.d.ts → jobs-CuRzTDb1.d.mts} +15 -13
  29. package/dist/{jobs-DJiISpqX.d.mts → jobs-DTnCZGtb.d.ts} +15 -13
  30. package/dist/paths.d.mts +6 -6
  31. package/dist/paths.d.ts +6 -6
  32. package/dist/paths.js +1 -1
  33. package/dist/paths.mjs +1 -1
  34. package/dist/roles-vY0AXhoc.d.mts +3 -3
  35. package/dist/roles-vY0AXhoc.d.ts +3 -3
  36. package/dist/route-contract.d.mts +16 -16
  37. package/dist/route-contract.d.ts +16 -16
  38. package/dist/route-contract.js +1 -1
  39. package/dist/route-contract.mjs +1 -1
  40. package/dist/{salaries-D6q7h1J6.d.ts → salaries-BOpR51Ny.d.mts} +3 -3
  41. package/dist/{salaries-B_Xb5k2s.d.mts → salaries-QoNpzRCT.d.ts} +3 -3
  42. package/dist/{search-CnOIZEuc.d.ts → search-B5ROY6PK.d.ts} +2 -2
  43. package/dist/{search-uC_ijbdu.d.mts → search-f93nrSg3.d.mts} +2 -2
  44. package/dist/seo.d.mts +17 -18
  45. package/dist/seo.d.ts +17 -18
  46. package/dist/server.d.mts +14 -32
  47. package/dist/server.d.ts +14 -32
  48. package/dist/sitemap.d.mts +7 -7
  49. package/dist/sitemap.d.ts +7 -7
  50. package/dist/sitemap.js +4 -2
  51. package/dist/sitemap.mjs +4 -2
  52. package/dist/skills.d.mts +1 -1
  53. package/dist/skills.d.ts +1 -1
  54. package/dist/skills.js +1 -1
  55. package/dist/suggest.d.mts +2 -2
  56. package/dist/suggest.d.ts +2 -2
  57. package/dist/theme.d.mts +10 -13
  58. package/dist/theme.d.ts +10 -13
  59. package/dist/theme.js +1 -1
  60. package/dist/theme.mjs +1 -1
  61. package/dist/{ui-copy-CKfFTtLk.d.mts → ui-copy-CL-VN1xb.d.mts} +2 -3
  62. package/dist/{ui-copy-CKfFTtLk.d.ts → ui-copy-CL-VN1xb.d.ts} +2 -3
  63. package/dist/well-known.d.mts +3 -3
  64. package/dist/well-known.d.ts +3 -3
  65. package/dist/well-known.js +1 -1
  66. package/dist/well-known.mjs +1 -1
  67. package/package.json +46 -38
  68. package/skills/cavuno-board-client/SKILL.md +1 -2
  69. package/skills/cavuno-board-filters/SKILL.md +2 -2
  70. package/skills/cavuno-board-format/SKILL.md +1 -1
  71. package/skills/cavuno-board-i18n/SKILL.md +3 -3
  72. package/skills/cavuno-board-job-posting/SKILL.md +1 -1
  73. package/skills/cavuno-board-messaging/SKILL.md +2 -3
  74. package/skills/cavuno-board-paywall/SKILL.md +1 -1
  75. package/skills/cavuno-board-salaries/SKILL.md +1 -1
  76. package/skills/cavuno-board-seo/SKILL.md +4 -6
  77. package/skills/cavuno-board-server/SKILL.md +1 -1
  78. package/skills/cavuno-board-setup/SKILL.md +2 -1
  79. package/skills/cavuno-board-sitemap/SKILL.md +1 -1
  80. package/skills/cavuno-board-smoke-test/SKILL.md +4 -13
  81. package/skills/flavors/tanstack-start/SKILL.md +1 -2
  82. package/skills/manifest.json +1 -1
  83. package/dist/_spec-C7vqY_b3.d.mts +0 -5202
  84. package/dist/_spec-C7vqY_b3.d.ts +0 -5202
package/dist/bin.mjs CHANGED
@@ -394,86 +394,18 @@ async function runReadProbes(fetchImpl, frontendUrl) {
394
394
  ];
395
395
  }
396
396
 
397
- // src/doctor/sandbox.ts
398
- var SANDBOX_GATE = record("sandbox.persona-gate", 1);
399
- var EXPECTED_PERSONA_COUNT = 8;
400
- function skipSandboxPersonaGate(reason) {
401
- return SANDBOX_GATE("skip", reason);
402
- }
403
- async function checkSandboxPersonaGate(fetchImpl, env, boardContext) {
404
- if (boardContext === null) {
405
- return SANDBOX_GATE(
406
- "skip",
407
- "board did not resolve \u2014 fix static.board first"
408
- );
409
- }
410
- const url = `${apiBase(env.apiUrl)}/v1/boards/${encodeURIComponent(env.boardKey)}/sandbox/personas`;
411
- const result = await probe(fetchImpl, url);
412
- if (boardContext.sandbox !== true) {
413
- return result.status === 404 ? SANDBOX_GATE(
414
- "pass",
415
- "sandbox persona endpoint refuses (404) on a non-sandbox board"
416
- ) : SANDBOX_GATE(
417
- "fail",
418
- `sandbox persona endpoint is reachable on a NON-sandbox board (HTTP ${result.status}) \u2014 the capability gate is not holding`
419
- );
420
- }
421
- if (!result.ok) {
422
- return SANDBOX_GATE(
423
- "fail",
424
- `sandbox persona roster did not resolve (HTTP ${result.status}) \u2014 the persona seed may have failed`
425
- );
426
- }
427
- let personas;
428
- let password;
429
- try {
430
- const parsed = JSON.parse(result.body);
431
- personas = parsed.personas;
432
- password = parsed.password;
433
- } catch {
434
- return SANDBOX_GATE(
435
- "fail",
436
- "sandbox persona endpoint returned 200 but not JSON \u2014 proxy or captive portal in the way?"
437
- );
438
- }
439
- if (!Array.isArray(personas) || personas.length !== EXPECTED_PERSONA_COUNT) {
440
- return SANDBOX_GATE(
441
- "fail",
442
- `expected ${EXPECTED_PERSONA_COUNT} personas, got ${Array.isArray(personas) ? personas.length : "a non-array"} \u2014 the persona seed is incomplete`
443
- );
444
- }
445
- const unseeded = personas.filter((persona) => persona.seeded !== true);
446
- if (unseeded.length > 0) {
447
- const ids = unseeded.map((persona) => typeof persona.id === "string" ? persona.id : "?").join(", ");
448
- return SANDBOX_GATE(
449
- "fail",
450
- `${unseeded.length} of ${EXPECTED_PERSONA_COUNT} personas are not seeded on the board (${ids}) \u2014 the persona seed failed or was purged; run POST /v1/boards/{key}/sandbox/reseed to restore the roster`
451
- );
452
- }
453
- if (typeof password !== "string" || password.length === 0) {
454
- return SANDBOX_GATE(
455
- "fail",
456
- "roster present but the published password is missing"
457
- );
458
- }
459
- return SANDBOX_GATE(
460
- "pass",
461
- `sandbox roster complete and seeded (${EXPECTED_PERSONA_COUNT} personas)`
462
- );
463
- }
464
-
465
397
  // src/doctor/theme.ts
466
398
  import { createHash } from "crypto";
467
399
  import { existsSync as existsSync2, readFileSync as readFileSync3 } from "fs";
468
400
  import { join as join2 } from "path";
469
401
  var THEME = record("static.theme", 1);
470
- function checkThemeFreshness(projectRoot, context) {
402
+ function checkThemeFreshness(projectRoot) {
471
403
  const tokensPath = join2(projectRoot, "src/tokens.css");
472
404
  if (!existsSync2(tokensPath)) {
473
405
  return [
474
406
  THEME(
475
407
  "skip",
476
- "board is not tokens-migrated (ADR-0065) \u2014 no src/tokens.css"
408
+ "no src/tokens.css \u2014 local theme freshness check not applicable"
477
409
  )
478
410
  ];
479
411
  }
@@ -490,234 +422,11 @@ function checkThemeFreshness(projectRoot, context) {
490
422
  )
491
423
  ];
492
424
  }
493
- if (!context) {
494
- return [
495
- THEME(
496
- "skip",
497
- "local derivations fresh; platform snapshot unverified \u2014 board context unavailable"
498
- )
499
- ];
500
- }
501
- if (context.themeSnapshotHash !== tokensHash) {
502
- return [
503
- THEME(
504
- "fail",
505
- `platform theme snapshot is ${context.themeSnapshotHash ? "stale" : "missing"} \u2014 emails render ${context.themeSnapshotHash ? "an old" : "the legacy"} theme; sync it: \`npm run gen:theme -- --payload | npx convex run boards/themeSnapshot:sync\``
506
- )
507
- ];
508
- }
509
425
  return [
510
- THEME(
511
- "pass",
512
- `tokens.css \u21C4 resolved module \u21C4 platform snapshot (${tokensHash.slice(0, 12)}\u2026)`
513
- )
426
+ THEME("pass", `tokens.css \u21C4 resolved module (${tokensHash.slice(0, 12)}\u2026)`)
514
427
  ];
515
428
  }
516
429
 
517
- // src/doctor/writes.ts
518
- var WRITE = {
519
- board: record("write.board", 3),
520
- register: record("write.register", 3),
521
- login: record("write.login", 3),
522
- email: record("write.email", 3),
523
- apply: record("write.apply", 3),
524
- alert: record("write.alert", 3)
525
- };
526
- var DEPENDENT_CHECKS = [
527
- WRITE.register,
528
- WRITE.login,
529
- WRITE.email,
530
- WRITE.apply,
531
- WRITE.alert
532
- ];
533
- function skipWriteProbes(reason) {
534
- return Object.values(WRITE).map((make) => make("skip", reason));
535
- }
536
- var FETCH_TIMEOUT_MS2 = 1e4;
537
- var RESEND_API_BASE = "https://api.resend.com";
538
- async function request(fetchImpl, url, init) {
539
- try {
540
- const response = await fetchImpl(url, {
541
- signal: AbortSignal.timeout(FETCH_TIMEOUT_MS2),
542
- ...init
543
- });
544
- let json = null;
545
- try {
546
- json = await response.json();
547
- } catch {
548
- json = null;
549
- }
550
- return { ok: response.ok, status: response.status, json };
551
- } catch (error) {
552
- return {
553
- ok: false,
554
- status: 0,
555
- json: { error: error instanceof Error ? error.message : String(error) }
556
- };
557
- }
558
- }
559
- function post(fetchImpl, url, body, bearer) {
560
- return request(fetchImpl, url, {
561
- method: "POST",
562
- headers: {
563
- "content-type": "application/json",
564
- accept: "application/json",
565
- ...bearer ? { authorization: `Bearer ${bearer}` } : {}
566
- },
567
- body: JSON.stringify(body)
568
- });
569
- }
570
- var sleep = (ms) => new Promise((r) => setTimeout(r, ms));
571
- async function observeVerificationEmail(fetchImpl, apiKey, recipient, pollMs, timeoutMs) {
572
- const deadline = Date.now() + timeoutMs;
573
- const subjectMarker = `[To: ${recipient}]`;
574
- do {
575
- const list = await request(
576
- fetchImpl,
577
- `${RESEND_API_BASE}/emails?limit=20`,
578
- {
579
- headers: { authorization: `Bearer ${apiKey}` }
580
- }
581
- );
582
- if (!list.ok) {
583
- return WRITE.email(
584
- "fail",
585
- `Resend list returned HTTP ${list.status} \u2014 wrong RESEND_API_KEY?`
586
- );
587
- }
588
- const items = list.json?.data ?? [];
589
- const match = items.find(
590
- (item) => item.subject?.includes(subjectMarker) || item.to?.includes(recipient)
591
- );
592
- if (match) {
593
- return WRITE.email(
594
- "pass",
595
- `verification send observed in Resend for ${recipient}`
596
- );
597
- }
598
- await sleep(pollMs);
599
- } while (Date.now() < deadline);
600
- return WRITE.email(
601
- "fail",
602
- `no verification send for ${recipient} appeared in Resend within ${timeoutMs}ms`
603
- );
604
- }
605
- async function runWriteProbes(options) {
606
- const { fetchImpl } = options;
607
- const base = `${apiBase(options.env.apiUrl)}/v1/boards/${encodeURIComponent(options.env.boardKey)}`;
608
- const nonce = options.nonce ?? `${Date.now().toString(36)}${Math.random().toString(36).slice(2, 8)}`;
609
- const email = `doctor+${nonce}@cavuno.com`;
610
- const password = `doctor-probe-${nonce}-A1!`;
611
- let context = options.boardContext ?? null;
612
- if (context === null) {
613
- const r = await request(fetchImpl, base);
614
- if (!r.ok) {
615
- return [
616
- WRITE.board("fail", `board context did not resolve (HTTP ${r.status})`),
617
- ...DEPENDENT_CHECKS.map(
618
- (make) => make("skip", "not run \u2014 the write gate failed (see write.board)")
619
- )
620
- ];
621
- }
622
- context = r.json ?? {};
623
- }
624
- if (context.sandbox !== true) {
625
- return [
626
- WRITE.board(
627
- "fail",
628
- "refusing write probes: this board is not the platform sandbox (`sandbox` is not true) \u2014 point PUBLIC_CAVUNO_BOARD at the sandbox pk_"
629
- ),
630
- ...DEPENDENT_CHECKS.map(
631
- (make) => make("skip", "not run \u2014 the write gate failed (see write.board)")
632
- )
633
- ];
634
- }
635
- const results = [
636
- WRITE.board("pass", "board is the platform sandbox")
637
- ];
638
- const registered = await post(fetchImpl, `${base}/auth/register`, {
639
- role: "candidate",
640
- method: "emailpass",
641
- email,
642
- password,
643
- displayName: "Doctor Probe"
644
- });
645
- const sessionOk = (r) => r.ok && typeof r.json?.accessToken === "string";
646
- results.push(
647
- sessionOk(registered) ? WRITE.register("pass", `candidate registered (${email})`) : WRITE.register(
648
- "fail",
649
- `register returned HTTP ${registered.status}` + (registered.status === 429 ? " \u2014 rate-limited: register allows 5 signups / 15 min per IP; wait before rerunning" : "")
650
- )
651
- );
652
- let accessToken = null;
653
- if (sessionOk(registered)) {
654
- const login = await post(fetchImpl, `${base}/auth/login`, {
655
- email,
656
- password
657
- });
658
- if (sessionOk(login)) {
659
- accessToken = login.json.accessToken;
660
- results.push(WRITE.login("pass", "emailpass login returns a session"));
661
- } else {
662
- results.push(WRITE.login("fail", `login returned HTTP ${login.status}`));
663
- }
664
- } else {
665
- results.push(WRITE.login("skip", "not run \u2014 register failed"));
666
- }
667
- if (!sessionOk(registered)) {
668
- results.push(WRITE.email("skip", "not run \u2014 register failed"));
669
- } else if (!options.resendApiKey) {
670
- results.push(
671
- WRITE.email(
672
- "skip",
673
- "RESEND_API_KEY not set \u2014 the verification send was NOT verified (platform-operator check)"
674
- )
675
- );
676
- } else {
677
- results.push(
678
- await observeVerificationEmail(
679
- fetchImpl,
680
- options.resendApiKey,
681
- email,
682
- options.emailPollMs ?? 2e3,
683
- options.emailTimeoutMs ?? 3e4
684
- )
685
- );
686
- }
687
- if (!accessToken) {
688
- results.push(WRITE.apply("skip", "not run \u2014 no session (see above)"));
689
- } else {
690
- const listing = await request(fetchImpl, `${base}/jobs?limit=1`);
691
- const card = listing.json?.data?.[0];
692
- if (!listing.ok || !card?.slug) {
693
- results.push(
694
- WRITE.apply(
695
- "fail",
696
- listing.ok ? "the board has no jobs to apply to \u2014 is the sandbox seeded?" : `jobs listing returned HTTP ${listing.status}`
697
- )
698
- );
699
- } else {
700
- const applied = await post(
701
- fetchImpl,
702
- `${base}/jobs/${encodeURIComponent(card.slug)}/apply`,
703
- {},
704
- accessToken
705
- );
706
- results.push(
707
- applied.ok && typeof applied.json?.id === "string" ? WRITE.apply("pass", `applied to ${card.slug}`) : WRITE.apply("fail", `apply returned HTTP ${applied.status}`)
708
- );
709
- }
710
- }
711
- const alert = await post(fetchImpl, `${base}/job-alerts`, {
712
- email,
713
- consent: true
714
- });
715
- results.push(
716
- alert.ok ? WRITE.alert("pass", "job-alert subscription accepted") : WRITE.alert("fail", `job-alerts returned HTTP ${alert.status}`)
717
- );
718
- return results;
719
- }
720
-
721
430
  // src/doctor/run.ts
722
431
  import { existsSync as existsSync3, readFileSync as readFileSync4 } from "fs";
723
432
  import { join as join3 } from "path";
@@ -754,31 +463,21 @@ async function checkBoardResolves(fetchImpl, env) {
754
463
  `${apiBase(env.apiUrl)}/v1/boards/${encodeURIComponent(env.boardKey)}`
755
464
  );
756
465
  if (!result.ok) {
757
- return {
758
- result: STATIC_BOARD(
759
- "fail",
760
- `board did not resolve for this key (HTTP ${result.status}) \u2014 revoked or wrong key?`
761
- ),
762
- context: null
763
- };
466
+ return STATIC_BOARD(
467
+ "fail",
468
+ `board did not resolve for this key (HTTP ${result.status}) \u2014 revoked or wrong key?`
469
+ );
764
470
  }
765
- let parsed = null;
766
471
  try {
767
- parsed = JSON.parse(result.body);
472
+ const parsed = JSON.parse(result.body);
768
473
  if (typeof parsed !== "object" || parsed === null) throw new Error("shape");
769
474
  } catch {
770
- return {
771
- result: STATIC_BOARD(
772
- "fail",
773
- "board endpoint returned 200 but not JSON \u2014 proxy or captive portal in the way?"
774
- ),
775
- context: null
776
- };
475
+ return STATIC_BOARD(
476
+ "fail",
477
+ "board endpoint returned 200 but not JSON \u2014 proxy or captive portal in the way?"
478
+ );
777
479
  }
778
- return {
779
- result: STATIC_BOARD("pass", "publishable key resolves the board"),
780
- context: parsed
781
- };
480
+ return STATIC_BOARD("pass", "publishable key resolves the board");
782
481
  }
783
482
  var SKILL_ROOTS = [
784
483
  ".claude/skills",
@@ -841,46 +540,19 @@ async function runDoctor(options) {
841
540
  } else {
842
541
  results.push(STATIC_API("skip", "PUBLIC_CAVUNO_API_URL not set"));
843
542
  }
844
- let boardContext = null;
845
543
  if (envOk) {
846
- const resolved = await checkBoardResolves(fetchImpl, env);
847
- boardContext = resolved.context;
848
- results.push(resolved.result);
544
+ results.push(await checkBoardResolves(fetchImpl, env));
849
545
  } else {
850
546
  results.push(STATIC_BOARD("skip", "env checks failed \u2014 fix them first"));
851
547
  }
852
548
  results.push(checkSkillsFreshness(options.projectRoot ?? process.cwd()));
853
- results.push(
854
- ...checkThemeFreshness(options.projectRoot ?? process.cwd(), boardContext)
855
- );
549
+ results.push(...checkThemeFreshness(options.projectRoot ?? process.cwd()));
856
550
  results.push(
857
551
  ...checkCookieCodecConformance(options.projectRoot ?? process.cwd())
858
552
  );
859
- results.push(
860
- envOk ? await checkSandboxPersonaGate(fetchImpl, env, boardContext) : skipSandboxPersonaGate("env checks failed \u2014 fix them first")
861
- );
862
553
  results.push(
863
554
  ...options.frontendUrl ? await runReadProbes(fetchImpl, options.frontendUrl) : skipReadProbes("no --frontend <url> given")
864
555
  );
865
- if (!options.sandbox) {
866
- results.push(
867
- ...skipWriteProbes(
868
- "write probes not enabled \u2014 run with --sandbox against the platform sandbox"
869
- )
870
- );
871
- } else if (!envOk) {
872
- results.push(...skipWriteProbes("env checks failed \u2014 fix them first"));
873
- } else {
874
- results.push(
875
- ...await runWriteProbes({
876
- env,
877
- fetchImpl,
878
- resendApiKey: options.resendApiKey,
879
- nonce: options.writeProbeNonce,
880
- boardContext
881
- })
882
- );
883
- }
884
556
  return { results, summary: summarize(results) };
885
557
  }
886
558
 
@@ -932,11 +604,7 @@ async function doctor(argv) {
932
604
  apiUrl: process.env.PUBLIC_CAVUNO_API_URL,
933
605
  boardKey: process.env.PUBLIC_CAVUNO_BOARD
934
606
  },
935
- frontendUrl,
936
- // Tier-3 write probes: opt-in via --sandbox, and refused server-side
937
- // unless the resolved board is platform-marked test (writes.ts gate).
938
- sandbox: argv.includes("--sandbox"),
939
- resendApiKey: process.env.RESEND_API_KEY
607
+ frontendUrl
940
608
  });
941
609
  console.log("\n@cavuno/board doctor");
942
610
  for (const result of results) {
@@ -1,11 +1,11 @@
1
- import { S as Schemas } from './_spec-C7vqY_b3.js';
1
+ import { S as Schemas } from './_spec-sW85fuvT.js';
2
2
 
3
3
  type PublicBoard = Schemas['PublicBoardContext'];
4
4
  type PublicBoardFeatures = PublicBoard['features'];
5
5
  type PublicBoardAnalytics = PublicBoard['analytics'];
6
6
  type PublicBoardTheme = NonNullable<PublicBoard['theme']>;
7
7
  /**
8
- * An operator-defined custom job-field definition (CAV-294). Board-wide;
8
+ * An operator-defined custom job-field definition. Board-wide;
9
9
  * use it to render and localize a job's opaque `customFieldValues` (resolve
10
10
  * option `key`s → labels, honour field `type` and display order). Shared with
11
11
  * the Operator API's custom-field surface (one canonical schema).
@@ -1,11 +1,11 @@
1
- import { S as Schemas } from './_spec-C7vqY_b3.mjs';
1
+ import { S as Schemas } from './_spec-sW85fuvT.mjs';
2
2
 
3
3
  type PublicBoard = Schemas['PublicBoardContext'];
4
4
  type PublicBoardFeatures = PublicBoard['features'];
5
5
  type PublicBoardAnalytics = PublicBoard['analytics'];
6
6
  type PublicBoardTheme = NonNullable<PublicBoard['theme']>;
7
7
  /**
8
- * An operator-defined custom job-field definition (CAV-294). Board-wide;
8
+ * An operator-defined custom job-field definition. Board-wide;
9
9
  * use it to render and localize a job's opaque `customFieldValues` (resolve
10
10
  * option `key`s → labels, honour field `type` and display order). Shared with
11
11
  * the Operator API's custom-field surface (one canonical schema).
package/dist/doctor.d.mts CHANGED
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Doctor check primitives (ADR-0058 Wave B): the pure logic behind
2
+ * Doctor check primitives: the pure logic behind
3
3
  * `npx @cavuno/board doctor` — env validation, JSON-LD extraction,
4
4
  * sitemap parsing, and the pass/fail/skip summary. Everything here is
5
5
  * synchronous and I/O-free so it unit-tests without a network; the
@@ -8,8 +8,8 @@
8
8
  type CheckStatus = 'pass' | 'fail' | 'skip';
9
9
  interface CheckResult {
10
10
  id: string;
11
- /** 1 static · 2 read probes · 3 write probes (sandbox-only). */
12
- tier: 1 | 2 | 3;
11
+ /** 1 static · 2 read probes. */
12
+ tier: 1 | 2;
13
13
  status: CheckStatus;
14
14
  detail: string;
15
15
  }
@@ -25,13 +25,9 @@ interface DoctorSummary {
25
25
  }
26
26
 
27
27
  /**
28
- * Doctor orchestrator (ADR-0058 Wave B): tier-1 static checks (this
29
- * file), tier-2 read probes (read.ts), and opt-in tier-3 write probes
30
- * (writes.ts — sandbox-only, gated on the board context's `sandbox`
31
- * flag). Each tier is a run/skip module pair dispatched the same way, so
32
- * every check always appears in the results — unrun checks SKIP loudly,
33
- * nothing silently vanishes. Fetch is injected for tests; the CLI passes
34
- * global fetch.
28
+ * Public project diagnostics: static configuration checks and optional read
29
+ * probes against the consumer's frontend. The command is intentionally
30
+ * non-destructive and needs no Cavuno operator credentials.
35
31
  */
36
32
  interface RunDoctorOptions {
37
33
  env: DoctorEnv;
@@ -40,15 +36,6 @@ interface RunDoctorOptions {
40
36
  /** Consumer project root for the skills-freshness check. Default: cwd. */
41
37
  projectRoot?: string;
42
38
  fetchImpl?: typeof fetch;
43
- /**
44
- * Enable tier-3 write probes (`--sandbox`). The probes themselves gate
45
- * on the resolved board's `sandbox` flag; this option only opts in.
46
- */
47
- sandbox?: boolean;
48
- /** Platform-operator Resend credential for the email-boundary check. */
49
- resendApiKey?: string;
50
- /** Test override for the per-run probe identity. */
51
- writeProbeNonce?: string;
52
39
  }
53
40
  interface DoctorRun {
54
41
  results: CheckResult[];
package/dist/doctor.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Doctor check primitives (ADR-0058 Wave B): the pure logic behind
2
+ * Doctor check primitives: the pure logic behind
3
3
  * `npx @cavuno/board doctor` — env validation, JSON-LD extraction,
4
4
  * sitemap parsing, and the pass/fail/skip summary. Everything here is
5
5
  * synchronous and I/O-free so it unit-tests without a network; the
@@ -8,8 +8,8 @@
8
8
  type CheckStatus = 'pass' | 'fail' | 'skip';
9
9
  interface CheckResult {
10
10
  id: string;
11
- /** 1 static · 2 read probes · 3 write probes (sandbox-only). */
12
- tier: 1 | 2 | 3;
11
+ /** 1 static · 2 read probes. */
12
+ tier: 1 | 2;
13
13
  status: CheckStatus;
14
14
  detail: string;
15
15
  }
@@ -25,13 +25,9 @@ interface DoctorSummary {
25
25
  }
26
26
 
27
27
  /**
28
- * Doctor orchestrator (ADR-0058 Wave B): tier-1 static checks (this
29
- * file), tier-2 read probes (read.ts), and opt-in tier-3 write probes
30
- * (writes.ts — sandbox-only, gated on the board context's `sandbox`
31
- * flag). Each tier is a run/skip module pair dispatched the same way, so
32
- * every check always appears in the results — unrun checks SKIP loudly,
33
- * nothing silently vanishes. Fetch is injected for tests; the CLI passes
34
- * global fetch.
28
+ * Public project diagnostics: static configuration checks and optional read
29
+ * probes against the consumer's frontend. The command is intentionally
30
+ * non-destructive and needs no Cavuno operator credentials.
35
31
  */
36
32
  interface RunDoctorOptions {
37
33
  env: DoctorEnv;
@@ -40,15 +36,6 @@ interface RunDoctorOptions {
40
36
  /** Consumer project root for the skills-freshness check. Default: cwd. */
41
37
  projectRoot?: string;
42
38
  fetchImpl?: typeof fetch;
43
- /**
44
- * Enable tier-3 write probes (`--sandbox`). The probes themselves gate
45
- * on the resolved board's `sandbox` flag; this option only opts in.
46
- */
47
- sandbox?: boolean;
48
- /** Platform-operator Resend credential for the email-boundary check. */
49
- resendApiKey?: string;
50
- /** Test override for the per-run probe identity. */
51
- writeProbeNonce?: string;
52
39
  }
53
40
  interface DoctorRun {
54
41
  results: CheckResult[];