@alphazede/bearing-lite 0.2.2 → 1.0.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 (57) hide show
  1. package/CONTRIBUTING.md +11 -6
  2. package/README.md +224 -131
  3. package/com.github.copilot/hooks/hooks.json +33 -0
  4. package/hooks/assurance-budget.cjs +33 -11
  5. package/hooks/com.anthropic.claude-code/mapping.md +35 -16
  6. package/hooks/plan-package.cjs +5 -4
  7. package/hooks/policy.cjs +10 -4
  8. package/hooks/profiles.cjs +119 -0
  9. package/hooks/te-host.cjs +40 -5
  10. package/hooks/transition-order.cjs +12 -4
  11. package/hooks/verification.cjs +358 -0
  12. package/package.json +6 -3
  13. package/plugin.json +2 -34
  14. package/profiles.json +1 -0
  15. package/schemas/implementation.schema.json +204 -5
  16. package/schemas/journey.schema.json +3 -3
  17. package/schemas/profiles.schema.json +359 -0
  18. package/schemas/verification.schema.json +126 -0
  19. package/skills/{set-bearings → architectural-alignment}/SKILL.md +17 -13
  20. package/skills/{set-bearings → architectural-alignment}/templates/workspace.md +1 -1
  21. package/skills/bearing-lite/SKILL.md +22 -22
  22. package/skills/bearing-lite/references/assurance-policy.md +32 -16
  23. package/skills/bearing-lite/references/lineups.md +7 -110
  24. package/skills/bearing-lite/references/owner-stops.md +13 -13
  25. package/skills/bearing-lite/references/peer-synthesis.md +11 -12
  26. package/skills/bearing-lite/references/profiles.md +149 -0
  27. package/skills/bearing-lite/references/resume.md +30 -0
  28. package/skills/bearing-lite/references/review-policy.md +3 -3
  29. package/skills/bearing-lite/references/role-routing.mmd +14 -14
  30. package/skills/bearing-lite/references/task-state.md +10 -10
  31. package/skills/bearing-lite/references/task-state.mmd +2 -2
  32. package/skills/bearing-lite/references/verification.md +52 -0
  33. package/skills/bearing-lite/templates/task.md +29 -28
  34. package/skills/{explorer → coordinator}/SKILL.md +16 -16
  35. package/skills/{crewmate → implementer}/SKILL.md +17 -15
  36. package/skills/{repository-fit → intake}/SKILL.md +10 -9
  37. package/skills/integration-engineer/SKILL.md +17 -12
  38. package/skills/light-implementer/SKILL.md +6 -5
  39. package/skills/onboard-bearing/SKILL.md +46 -0
  40. package/skills/plan-integrator/SKILL.md +14 -14
  41. package/skills/{map-the-route → planning-and-design}/SKILL.md +27 -27
  42. package/skills/{map-the-route → planning-and-design}/references/artifact-grammar.md +29 -27
  43. package/skills/prompt/SKILL.md +245 -0
  44. package/skills/requirements-engineer/SKILL.md +11 -11
  45. package/skills/{park-ranger → reviewer}/SKILL.md +16 -13
  46. package/skills/{gather-supplies → scope-definition}/SKILL.md +13 -13
  47. package/skills/scribe/SKILL.md +8 -8
  48. package/skills/systems-modeler/SKILL.md +5 -3
  49. package/skills/test-engineer/SKILL.md +16 -13
  50. package/templates/dod-manifest-v1.html +648 -0
  51. package/tools/render-dod-manifest.mjs +1743 -0
  52. package/lineups.json +0 -1
  53. package/schemas/lineups.schema.json +0 -145
  54. package/skills/navigator/SKILL.md +0 -36
  55. package/skills/surveyor/SKILL.md +0 -48
  56. package/skills/validator/SKILL.md +0 -37
  57. package/skills/validator/references/grading-rubric.md +0 -40
@@ -17,9 +17,13 @@ procedural.
17
17
  | AGY | `.agy/` (strict `plugin.json`) | none | skills-only |
18
18
  | Pi | `package.json` `"pi"` + `pi-package` | none (TypeScript extensions, not command hooks) | skills-only |
19
19
  | DeepCode | `.deepcode/skills` or interoperable `.agents/skills` discovery | none | skills-only |
20
+ | GitHub Copilot | Agent Plugins 1.0 `plugin.json` + `com.github.copilot/` | `com.github.copilot/hooks/hooks.json` (`SessionStart`, `PreToolUse`, `Stop`, `SubagentStop`) | partial |
20
21
 
21
- Do **not** set `hooks` on Claude, Codex, or Grok host manifests. Those hosts
22
- auto-load `hooks/hooks.json`; declaring both is a duplicate-file error.
22
+ Do **not** set `hooks` on the root Agent Plugins 1.0 `plugin.json`, or on Claude,
23
+ Codex, or Grok host manifests. Root `hooks` is outside the closed Agent Plugins
24
+ 1.0 field set. Claude, Codex, and Grok auto-load `hooks/hooks.json`; declaring
25
+ both is a duplicate-file error. GitHub Copilot in VS Code discovers plugin hooks
26
+ from `com.github.copilot/hooks/hooks.json`.
23
27
 
24
28
  Skill-copy into a host skills directory does **not** register hooks. Plugin
25
29
  install or disable uses the host's native controls. Bearing Lite never copies
@@ -43,19 +47,19 @@ registered separately, and a `Stop` event reaches both.
43
47
  The shared planning-review evaluator is used by transition and closeout when a
44
48
  client supplies a structured `planning_review` record. Current session-start /
45
49
  stop mappings cannot derive that nested record safely, so Claude Code, Codex,
46
- Grok Build, Cursor, and Kimi remain partial and the check is procedural there.
47
- AGY, Pi, and DeepCode remain skills-only. Do not claim full planning-review
48
- enforcement for any of these hosts until a native event supplies the complete
49
- record.
50
+ Grok Build, Cursor, Kimi, and GitHub Copilot remain partial and the check is
51
+ procedural there. AGY, Pi, and DeepCode remain skills-only. Do not claim full
52
+ planning-review enforcement for any of these hosts until a native event supplies
53
+ the complete record.
50
54
 
51
55
  The per-declared-phase-or-wave assurance record
52
56
  (`skills/bearing-lite/references/assurance-policy.md`) is evaluated by the same
53
57
  `transition` class through the `assurance_transition` action_kind. No mapped
54
58
  host exposes a hook event that carries the frozen declaration and the visible
55
59
  task record, so the assurance budget is procedural on every mapped host:
56
- Claude Code, Codex, Grok Build, Cursor, and Kimi Code stay partial, and AGY,
57
- Pi, and DeepCode stay skills-only. No host may advertise an enforcement of the
58
- assurance budget it cannot perform.
60
+ Claude Code, Codex, Grok Build, Cursor, Kimi Code, and GitHub Copilot stay
61
+ partial, and AGY, Pi, and DeepCode stay skills-only. No host may advertise an
62
+ enforcement of the assurance budget it cannot perform.
59
63
 
60
64
  The adapter accepts snake_case and camelCase (`hook_event_name` /
61
65
  `hookEventName`, `cwd` / `workspaceRoot`).
@@ -108,13 +112,24 @@ this adapter must not advertise enforcement it cannot perform.
108
112
  | Pi | te_test_write UNAVAILABLE | te_completion UNAVAILABLE | child te_completion UNAVAILABLE |
109
113
  | AGY | te_test_write UNAVAILABLE | te_completion UNAVAILABLE | child te_completion UNAVAILABLE |
110
114
  | DeepCode | te_test_write UNAVAILABLE | te_completion UNAVAILABLE | child te_completion UNAVAILABLE |
115
+ | GitHub Copilot | native `PreToolUse` deny | native `Stop` deny | native `SubagentStop` deny |
111
116
 
112
117
  Cursor's `stop` guidance message is advisory text, not a hard completion
113
118
  block, and Cursor exposes no distinct hard child-stop deny; neither is
114
119
  registered as one. Kimi Code, Pi, AGY, and DeepCode carry no verified native
115
- Test Engineering blocking at all, so `hooks/te-host.cjs` returns `UNAVAILABLE`
116
- for every class on those hosts rather than claiming an enforcement it cannot
117
- deliver.
120
+ Test Engineering blocking in this adapter, so `hooks/te-host.cjs` returns
121
+ `UNAVAILABLE` for every class on those hosts rather than claiming an
122
+ enforcement it cannot deliver.
123
+
124
+ GitHub Copilot in VS Code is a native TE host. Agent Plugins 1.0 discovers
125
+ `com.github.copilot/hooks/hooks.json` and expands `${PLUGIN_ROOT}`; the TE
126
+ commands quote that path and pass `--host=copilot`. Copilot `PreToolUse` denies
127
+ through `hookSpecificOutput.permissionDecision`. Copilot `Stop` denies through
128
+ `hookSpecificOutput` with `hookEventName` `Stop`, `decision` `block`, and
129
+ `reason`. Copilot `SubagentStop` denies through a top-level `decision` block
130
+ and `reason`. `Stop` and `SubagentStop` carry `stop_hook_active`; a true
131
+ re-entry terminates as quiet success so a deny cannot loop. Transition-order,
132
+ protected-action, planning-review, and assurance-budget stay procedural.
118
133
 
119
134
  ## Derived fields
120
135
 
@@ -134,10 +149,10 @@ trees) and sets only:
134
149
  payload. Missing values stay missing, so activation advises the router instead
135
150
  of inventing context.
136
151
 
137
- The Router writes the visible `checkout_lease` before any planning write or
138
- dispatch; its nested non-placeholder Journey identity is the Router-invoked
139
- signal during planning. The plan-level Explorer-versus-Expedition value is
140
- recorded later at the route review.
152
+ The Orchestrator writes the visible `checkout_lease` before any planning write
153
+ or dispatch; its nested non-placeholder Lifecycle identity is the
154
+ Router-invoked signal during planning. The plan-level lifecycle setting is
155
+ recorded later at the integrated owner review.
141
156
 
142
157
  ## Outcome translation
143
158
 
@@ -148,6 +163,10 @@ recorded later at the route review.
148
163
  | Stop/SubagentStop with no discoverable Journey | quiet success (empty JSON; no `additionalContext`) | always `0` |
149
164
  | first-pass Stop/SubagentStop with a discoverable Journey | `hookSpecificOutput.additionalContext` | always `0` |
150
165
  | `BLOCK` | JSON `decision: "block"` only; this mapping never requests protected completion | always `0` |
166
+ | Copilot `PreToolUse` deny | `hookSpecificOutput.permissionDecision` `"deny"` | always `0` |
167
+ | Copilot `Stop` deny | `hookSpecificOutput` `{ hookEventName: "Stop", decision: "block", reason }` | always `0` |
168
+ | Copilot `SubagentStop` deny | top-level `{ decision: "block", reason }` | always `0` |
169
+ | Copilot `Stop`/`SubagentStop` re-entry (`stop_hook_active`) | quiet success (empty JSON) | always `0` |
151
170
 
152
171
  A discoverable Journey is a visible plan with a `task_id`, `assigned_role`,
153
172
  non-placeholder journey marker, or `checkout_lease` block. Empty cwd and
@@ -4,7 +4,7 @@
4
4
  * Map the Route freeze (#70, #73, #77, #80): pure checks over a planning package.
5
5
  * checkRoles: every command a slice runs whose seit procedure names an actor
6
6
  * must name the slice's role. checkWorkClass validates light slices;
7
- * checkPlanningRoles excludes planning-only roles from Expedition slices.
7
+ * checkPlanningRoles excludes planning-only roles from Lifecycle implementation slices.
8
8
  * verifyDigests: every planning input digest
9
9
  * embedded in seit.json matches the file on disk; the manifest digest over
10
10
  * those inputs plus seit.json matches implementation.json's
@@ -70,13 +70,14 @@ function checkWorkClass(node, findings = []) {
70
70
  return findings;
71
71
  }
72
72
 
73
- /** #80: Requirements Engineer is planning-only and never an Expedition slice. */
73
+ /** #80: Requirements Engineer is planning-only and never a Lifecycle implementation slice. */
74
74
  function checkPlanningRoles(node, findings = []) {
75
75
  if (Array.isArray(node)) node.forEach((item) => checkPlanningRoles(item, findings));
76
76
  else if (node && typeof node === "object") {
77
- if (typeof node.role === "string" && node.role.startsWith("Requirements Engineer")) {
77
+ const isSlice = typeof node.id === "string" || Array.isArray(node.command_ids);
78
+ if (isSlice && typeof node.role === "string" && node.role.startsWith("Requirements Engineer")) {
78
79
  findings.push({
79
- code: "planning_role_in_expedition",
80
+ code: "planning_role_in_lifecycle",
80
81
  step: typeof node.id === "string" ? node.id : null,
81
82
  role: node.role,
82
83
  });
package/hooks/policy.cjs CHANGED
@@ -8,8 +8,8 @@
8
8
  */
9
9
 
10
10
  const PLANNING_REVIEW_POLICY = Object.freeze({
11
- reviewer_slots_min: 2,
12
- reviewer_slots_max: 2,
11
+ reviewer_slots_min: 1,
12
+ reviewer_slots_max: 1,
13
13
  independence_required: true,
14
14
  isolated_findings_until_aggregation: true,
15
15
  candidate_fields: ["candidate_ref", "candidate_revision", "candidate_digest"],
@@ -23,12 +23,18 @@ const PLANNING_REVIEW_POLICY = Object.freeze({
23
23
  });
24
24
 
25
25
  const ASSURANCE_BUDGET_POLICY = Object.freeze({
26
- budget_scope: "per_declared_phase_or_wave",
26
+ budget_scope: "per_declared_cadence_unit",
27
+ cadence_values: ["slice", "phase", "lifecycle"],
28
+ default_cadence: {
29
+ "test_engineer.assurance": "phase",
30
+ reviewer: "phase",
31
+ "integration_engineer.execution": "lifecycle",
32
+ },
27
33
  review_rounds: 1,
28
34
  aggregated_repairs_max: 1,
29
35
  post_repair_gate: "deterministic_PASS",
30
36
  automatic_phase_or_wave_end_review: "required",
31
- automatic_per_slice_review: "prohibited",
37
+ automatic_per_slice_review: "cadence_gated",
32
38
  post_repair_rereview: "prohibited",
33
39
  automatic_rereview_of_same_unit: "prohibited",
34
40
  budget_reset_on_candidate_change: false,
@@ -0,0 +1,119 @@
1
+ "use strict";
2
+
3
+ /**
4
+ * Profile catalog presence and migration classifier (DES-BDL-004/005).
5
+ * Pure evaluator: never writes, never treats lineups.json as live configuration.
6
+ */
7
+
8
+ const fs = require("node:fs");
9
+ const path = require("node:path");
10
+
11
+ const PACKAGED_BYTES = '{"schema_version":1,"profiles":{}}';
12
+ const NAME_PATTERN = /^[A-Za-z][A-Za-z0-9_-]*$/;
13
+ const RETIRED_ROLE_KEYS = new Set([
14
+ "surveyor",
15
+ "explorer",
16
+ "crewmate",
17
+ "navigator",
18
+ "park_ranger",
19
+ "park-ranger",
20
+ "validator",
21
+ ]);
22
+
23
+ function isPlainObject(value) {
24
+ return value !== null && typeof value === "object" && !Array.isArray(value);
25
+ }
26
+
27
+ function resolveHome(env) {
28
+ const home = typeof env.HOME === "string" ? env.HOME.trim() : "";
29
+ if (home && path.isAbsolute(home)) return home;
30
+ const profile = typeof env.USERPROFILE === "string" ? env.USERPROFILE.trim() : "";
31
+ if (profile && path.isAbsolute(profile)) return profile;
32
+ return null;
33
+ }
34
+
35
+ function exists(file) {
36
+ try {
37
+ return fs.existsSync(file);
38
+ } catch {
39
+ return false;
40
+ }
41
+ }
42
+
43
+ function readJson(file) {
44
+ try {
45
+ return JSON.parse(fs.readFileSync(file, "utf8"));
46
+ } catch {
47
+ return null;
48
+ }
49
+ }
50
+
51
+ function retiredRoleKeys(profile) {
52
+ if (!isPlainObject(profile) || !isPlainObject(profile.roles)) return [];
53
+ return Object.keys(profile.roles).filter((key) => RETIRED_ROLE_KEYS.has(key));
54
+ }
55
+
56
+ /**
57
+ * @param {object} [input]
58
+ * @returns {{ outcome: string, reason: string, path?: string }}
59
+ */
60
+ function evaluateCatalogState(input) {
61
+ const env = (input && input.env) || process.env;
62
+ const home = resolveHome(env);
63
+ if (!home) {
64
+ return { outcome: "NEEDS_MORE_EVIDENCE", reason: "home_unresolved" };
65
+ }
66
+ const dir = path.join(home, ".agents", "bearing-lite");
67
+ const profilesPath = path.join(dir, "profiles.json");
68
+ const lineupsPath = path.join(dir, "lineups.json");
69
+
70
+ const hasLineups = exists(lineupsPath);
71
+ const hasProfiles = exists(profilesPath);
72
+
73
+ if (hasLineups) {
74
+ return {
75
+ outcome: "MIGRATION_REQUIRED",
76
+ reason: "legacy_lineups_present",
77
+ path: lineupsPath,
78
+ };
79
+ }
80
+ if (!hasProfiles) {
81
+ return { outcome: "no_named_profiles", reason: "missing_user_catalog", path: profilesPath };
82
+ }
83
+
84
+ const parsed = readJson(profilesPath);
85
+ if (!isPlainObject(parsed)) {
86
+ return { outcome: "NEEDS_MORE_EVIDENCE", reason: "catalog_not_json", path: profilesPath };
87
+ }
88
+ if ("lineups" in parsed) {
89
+ return { outcome: "MIGRATION_REQUIRED", reason: "legacy_lineups_field", path: profilesPath };
90
+ }
91
+ const profiles = parsed.profiles;
92
+ if (!isPlainObject(profiles)) {
93
+ return { outcome: "NEEDS_MORE_EVIDENCE", reason: "profiles_not_object", path: profilesPath };
94
+ }
95
+ for (const name of Object.keys(profiles)) {
96
+ if (!NAME_PATTERN.test(name)) {
97
+ return { outcome: "NEEDS_MORE_EVIDENCE", reason: "invalid_profile_name", path: profilesPath };
98
+ }
99
+ const retired = retiredRoleKeys(profiles[name]);
100
+ if (retired.length) {
101
+ return {
102
+ outcome: "NEEDS_MORE_EVIDENCE",
103
+ reason: "retired_role:" + retired[0],
104
+ path: profilesPath,
105
+ };
106
+ }
107
+ }
108
+ return { outcome: "READY", reason: "profiles_live", path: profilesPath };
109
+ }
110
+
111
+ function packagedCatalogBytes() {
112
+ return PACKAGED_BYTES;
113
+ }
114
+
115
+ module.exports = {
116
+ evaluateCatalogState,
117
+ packagedCatalogBytes,
118
+ PACKAGED_BYTES,
119
+ };
package/hooks/te-host.cjs CHANGED
@@ -69,6 +69,7 @@ const HOST_SUPPORT = Object.freeze({
69
69
  grok: FULL_NATIVE,
70
70
  codex: FULL_NATIVE,
71
71
  "claude-code": FULL_NATIVE,
72
+ copilot: FULL_NATIVE,
72
73
  cursor: Object.freeze({
73
74
  write_time_deny: NATIVE,
74
75
  completion_deny: UNAVAILABLE,
@@ -118,6 +119,10 @@ function presentString(value) {
118
119
  return trimmed;
119
120
  }
120
121
 
122
+ function isTruthyFlag(value) {
123
+ return value === true || value === "true";
124
+ }
125
+
121
126
  /** Normalize Claude, Codex, Grok, Cursor, and Kimi event spellings. */
122
127
  function normalizeEvent(name) {
123
128
  if (typeof name !== "string") return "";
@@ -508,6 +513,19 @@ function handle(envelope, options) {
508
513
  );
509
514
  }
510
515
 
516
+ if (
517
+ host === "copilot" &&
518
+ hookClass === TE_COMPLETION &&
519
+ (isTruthyFlag(input.stop_hook_active) || isTruthyFlag(input.stopHookActive))
520
+ ) {
521
+ return hostOutput(
522
+ hookClass,
523
+ ALLOW,
524
+ "stop_hook_active: re-entry terminates quietly",
525
+ { hookEventName: eventName, host, code: "stop_hook_active" }
526
+ );
527
+ }
528
+
511
529
  const root = path.resolve(
512
530
  presentString(input.cwd) || presentString(input.workspaceRoot) || process.cwd()
513
531
  );
@@ -522,12 +540,13 @@ function handle(envelope, options) {
522
540
  hookClass,
523
541
  "capability_inactive: test-engineering is unselected and not required; " +
524
542
  "the class fails open and this is not a failure",
525
- { hookEventName: eventName, code: resolved.code }
543
+ { hookEventName: eventName, host, code: resolved.code }
526
544
  );
527
545
  }
528
546
  if (resolved.status !== "ACTIVE" || !resolved.evaluator) {
529
547
  return unavailable(hookClass, resolved.message || "typed_capability_gap", {
530
548
  hookEventName: eventName,
549
+ host,
531
550
  code: resolved.code || "typed_capability_gap",
532
551
  recovery: resolved.recovery,
533
552
  });
@@ -545,7 +564,7 @@ function handle(envelope, options) {
545
564
  hookClass,
546
565
  "evaluator_failure: the test-engineering evaluator did not return a " +
547
566
  "verdict; enforcement is unavailable for this event",
548
- { hookEventName: eventName, code: "evaluator_failure" }
567
+ { hookEventName: eventName, host, code: "evaluator_failure" }
549
568
  );
550
569
  }
551
570
 
@@ -554,14 +573,14 @@ function handle(envelope, options) {
554
573
  return unavailable(
555
574
  hookClass,
556
575
  "evaluator_verdict_unrecognized: enforcement is unavailable for this event",
557
- { hookEventName: eventName, code: "evaluator_verdict_unrecognized" }
576
+ { hookEventName: eventName, host, code: "evaluator_verdict_unrecognized" }
558
577
  );
559
578
  }
560
579
 
561
580
  const reason =
562
581
  (isPlainObject(result) ? presentString(result.reason) : undefined) ||
563
582
  "test_engineering_verdict";
564
- return toHostResponse(hookClass, verdict, reason, { hookEventName: eventName });
583
+ return toHostResponse(hookClass, verdict, reason, { hookEventName: eventName, host });
565
584
  }
566
585
 
567
586
  function flag(value) {
@@ -602,7 +621,23 @@ function toWire(response) {
602
621
  },
603
622
  };
604
623
  }
605
- if (response.decision === "block") return { decision: "block", reason: response.reason };
624
+ if (response.decision === "block") {
625
+ // VS Code Stop deny is nested; Claude/Codex/Grok and VS Code SubagentStop
626
+ // keep the top-level decision block.
627
+ if (
628
+ presentString(inner.host) === "copilot" &&
629
+ normalizeEvent(inner.hookEventName) === "stop"
630
+ ) {
631
+ return {
632
+ hookSpecificOutput: {
633
+ hookEventName: "Stop",
634
+ decision: "block",
635
+ reason: response.reason,
636
+ },
637
+ };
638
+ }
639
+ return { decision: "block", reason: response.reason };
640
+ }
606
641
  return {};
607
642
  }
608
643
 
@@ -83,7 +83,7 @@ function normalizeAssurance(value) {
83
83
  return [String(value)].filter((s) => s && s !== "none");
84
84
  }
85
85
 
86
- /** Normalize role labels for order comparison (Validator vs validator vs park-ranger). */
86
+ /** Normalize role labels for order comparison (Validator vs validator vs reviewer). */
87
87
  function normalizeRoleKey(role) {
88
88
  return String(role)
89
89
  .trim()
@@ -94,15 +94,23 @@ function normalizeRoleKey(role) {
94
94
  /**
95
95
  * Map the next missing assurance role to the state that should receive it.
96
96
  * Assurance Test Engineer (or the retired Validator label) -> VALIDATING,
97
- * Park Ranger -> REVIEWING, Surveyor -> ACCEPTANCE.
97
+ * Reviewer (or retired Park Ranger) -> REVIEWING,
98
+ * Integration Engineer execution (or retired Surveyor) -> ACCEPTANCE.
98
99
  */
99
100
  function assuranceTargetState(role) {
100
101
  const key = normalizeRoleKey(role);
101
102
  if (key === "validator" || key === "assurance-test-engineer" || key === "test-engineer") {
102
103
  return "VALIDATING";
103
104
  }
104
- if (key === "park-ranger" || key === "parkranger") return "REVIEWING";
105
- if (key === "surveyor") return "ACCEPTANCE";
105
+ if (key === "reviewer" || key === "park-ranger" || key === "parkranger") return "REVIEWING";
106
+ if (
107
+ key === "surveyor" ||
108
+ key === "integration-engineer" ||
109
+ key === "integration-engineer-execution" ||
110
+ key === "execution-integration-engineer"
111
+ ) {
112
+ return "ACCEPTANCE";
113
+ }
106
114
  return null;
107
115
  }
108
116