@cassiomc1/forgeloop 1.3.0 → 1.6.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 (163) hide show
  1. package/.github/copilot-instructions.md +1 -0
  2. package/AGENTS.md +1 -0
  3. package/CLAUDE.md +1 -0
  4. package/DOCS_INDEX.md +20 -8
  5. package/EXECUTION_STATE.md +60 -0
  6. package/LOOP_ENGINEERING.md +135 -5
  7. package/LOOP_SYSTEM_DESIGN.md +54 -1
  8. package/PROTOCOL_INTEGRATION.md +87 -0
  9. package/QUALITY_SCORECARD.md +2 -0
  10. package/README.md +69 -9
  11. package/TERMINOLOGY.md +15 -0
  12. package/THIRD_PARTY_NOTICES.md +30 -0
  13. package/THREAT_MODEL.md +59 -1
  14. package/docs/ARTIFACT_REFERENCE.md +183 -0
  15. package/docs/CLI_REFERENCE.md +391 -6
  16. package/docs/CROSS_HARNESS_CONTINUITY.md +23 -0
  17. package/docs/DIAGNOSTIC_MODEL.md +181 -0
  18. package/docs/DOCUMENTATION_GUIDE.md +36 -13
  19. package/docs/EXECUTION_TRACE.md +76 -0
  20. package/docs/GETTING_STARTED.md +1 -0
  21. package/docs/MCP.md +159 -0
  22. package/docs/RECIPES.md +149 -0
  23. package/docs/RELEASE_CHECKLIST_1_4.md +38 -0
  24. package/docs/RELEASE_CHECKLIST_1_5_MCP.md +78 -0
  25. package/docs/TROUBLESHOOTING.md +217 -3
  26. package/docs/UNIVERSAL_INTEGRATION.md +48 -0
  27. package/docs/assets/diagrams/forgeloop-engineering-flow.html +13797 -0
  28. package/docs/assets/diagrams/forgeloop-engineering-flow.receipt.json +37 -0
  29. package/docs/assets/diagrams/forgeloop-engineering-flow.svg +5002 -0
  30. package/docs/diagrams/README.md +55 -0
  31. package/docs/diagrams/forgeloop-engineering-flow.workflow.json +122 -0
  32. package/docs/diagrams/manifest.json +42 -0
  33. package/docs/diagrams/reviews/forgeloop-engineering-flow.review.json +20 -0
  34. package/package.json +21 -8
  35. package/schemas/action.schema.json +100 -0
  36. package/schemas/approval.schema.json +51 -0
  37. package/schemas/capability-policy.schema.json +41 -0
  38. package/schemas/diagnostic-case.schema.json +85 -0
  39. package/schemas/execution-receipt.schema.json +16 -0
  40. package/schemas/hypothesis-disposition.schema.json +16 -0
  41. package/schemas/intervention.schema.json +27 -0
  42. package/schemas/policy-lock.schema.json +1 -0
  43. package/schemas/policy-snapshot.schema.json +2 -0
  44. package/schemas/task-recovery.schema.json +61 -0
  45. package/schemas/trajectory-evaluation.schema.json +64 -0
  46. package/schemas/trajectory-scenario.schema.json +42 -0
  47. package/src/cli.js +267 -347
  48. package/src/commands/action-authorize.js +41 -0
  49. package/src/commands/action-propose.js +10 -0
  50. package/src/commands/action-reconcile.js +10 -0
  51. package/src/commands/action-record.js +47 -0
  52. package/src/commands/action-show.js +10 -0
  53. package/src/commands/action-verify.js +10 -0
  54. package/src/commands/advance.js +7 -2
  55. package/src/commands/approval-request.js +64 -0
  56. package/src/commands/approval-resolve.js +10 -0
  57. package/src/commands/audit.js +5 -0
  58. package/src/commands/baseline.js +3 -3
  59. package/src/commands/eval.js +6 -0
  60. package/src/commands/history.js +18 -0
  61. package/src/commands/init.js +2 -2
  62. package/src/commands/inspect.js +55 -0
  63. package/src/commands/metrics.js +7 -0
  64. package/src/commands/next.js +8 -2
  65. package/src/commands/policy-discover.js +2 -2
  66. package/src/commands/progress.js +6 -2
  67. package/src/commands/record-diagnosis.js +37 -1
  68. package/src/commands/record-hypothesis-disposition.js +45 -0
  69. package/src/commands/record-intervention.js +35 -0
  70. package/src/commands/reflect.js +38 -0
  71. package/src/commands/report.js +9 -1
  72. package/src/commands/run-action.js +18 -0
  73. package/src/commands/status.js +17 -0
  74. package/src/commands/task-create.js +39 -1
  75. package/src/commands/task-list.js +14 -1
  76. package/src/commands/task-lock-status.js +2 -2
  77. package/src/commands/task-recover.js +202 -0
  78. package/src/commands/task-repair-legacy-recovery.js +417 -0
  79. package/src/commands/task-resume.js +172 -0
  80. package/src/commands/task-scope.js +23 -4
  81. package/src/commands/task-show.js +18 -4
  82. package/src/commands/trace.js +34 -0
  83. package/src/commands/validate-protocol.js +40 -15
  84. package/src/core/action-authorization.js +106 -0
  85. package/src/core/action-constants.js +86 -0
  86. package/src/core/action-execution.js +105 -0
  87. package/src/core/action-ledger-projection.js +302 -0
  88. package/src/core/action-model.js +581 -0
  89. package/src/core/action-readiness.js +141 -0
  90. package/src/core/action-reconciliation-policy.js +49 -0
  91. package/src/core/action-reconciliation.js +66 -0
  92. package/src/core/action-verification.js +111 -0
  93. package/src/core/actions.js +462 -0
  94. package/src/core/approvals.js +405 -0
  95. package/src/core/artifact-registry.js +60 -0
  96. package/src/core/audit.js +45 -4
  97. package/src/core/bundles.js +30 -0
  98. package/src/core/capability-policy.js +226 -0
  99. package/src/core/cli-command-definitions.js +260 -5
  100. package/src/core/command-executors.js +543 -0
  101. package/src/core/command-input.js +107 -0
  102. package/src/core/command-runtime.js +117 -0
  103. package/src/core/completion-artifacts.js +39 -15
  104. package/src/core/completion-ownership.js +88 -0
  105. package/src/core/completion-recovery-rebind.js +194 -0
  106. package/src/core/completion.js +70 -0
  107. package/src/core/continuity-reconciliation.js +24 -5
  108. package/src/core/diagnostic-model.js +396 -0
  109. package/src/core/diagnostic-projection.js +51 -0
  110. package/src/core/diagnostic-record.js +360 -0
  111. package/src/core/error-codes.js +461 -1
  112. package/src/core/events.js +171 -2
  113. package/src/core/execution-prerequisites.js +4 -1
  114. package/src/core/execution.js +26 -188
  115. package/src/core/failure-signature.js +70 -0
  116. package/src/core/failure-surface.js +57 -0
  117. package/src/core/filesystem.js +55 -6
  118. package/src/core/history.js +110 -0
  119. package/src/core/hypothesis-projection.js +85 -0
  120. package/src/core/information-gain-projection.js +283 -0
  121. package/src/core/information-gain.js +138 -0
  122. package/src/core/inspect.js +132 -7
  123. package/src/core/integration-invocation-policy.js +217 -0
  124. package/src/core/integration-limits.js +20 -0
  125. package/src/core/integration-resources.js +178 -0
  126. package/src/core/next-action-model.js +94 -0
  127. package/src/core/next-action.js +490 -3
  128. package/src/core/phase.js +42 -22
  129. package/src/core/policy-engine.js +113 -6
  130. package/src/core/preflight-consistency.js +31 -5
  131. package/src/core/preflight.js +19 -2
  132. package/src/core/prepared-execution.js +227 -0
  133. package/src/core/progress.js +41 -4
  134. package/src/core/project-root.js +21 -0
  135. package/src/core/protocol-info.js +61 -0
  136. package/src/core/protocol.js +14 -0
  137. package/src/core/receipt.js +1 -0
  138. package/src/core/reconcile-closure.js +35 -10
  139. package/src/core/recovery-history.js +116 -0
  140. package/src/core/reflection.js +305 -0
  141. package/src/core/resumability.js +57 -3
  142. package/src/core/schema-validation.js +9 -0
  143. package/src/core/strategy-analysis.js +97 -0
  144. package/src/core/task-claim-state.js +272 -0
  145. package/src/core/task-command.js +5 -1
  146. package/src/core/task-conflict-inspection.js +321 -0
  147. package/src/core/task-context.js +32 -29
  148. package/src/core/task-discovery.js +14 -1
  149. package/src/core/task-lock.js +216 -22
  150. package/src/core/task-paths.js +31 -2
  151. package/src/core/task-recovery-migration.js +192 -0
  152. package/src/core/task-recovery.js +205 -0
  153. package/src/core/task-scope.js +33 -1
  154. package/src/core/task-snapshot.js +53 -0
  155. package/src/core/templates.js +9 -0
  156. package/src/core/trace.js +548 -0
  157. package/src/core/trajectory-evaluation.js +71 -0
  158. package/src/core/trajectory-metrics.js +80 -0
  159. package/src/core/transaction.js +36 -2
  160. package/src/core/work-state.js +10 -5
  161. package/src/integration.js +47 -0
  162. package/docs/assets/forgeloop-flow.svg +0 -1
  163. package/docs/forgeloop-flow.mmd +0 -51
@@ -0,0 +1,178 @@
1
+ import path from "node:path";
2
+ import { runProtocolInfo } from "../commands/protocol-info.js";
3
+ import { readContract } from "./contract.js";
4
+ import { discoverTasks } from "./task-discovery.js";
5
+ import { resolveTaskClaimState } from "./task-claim-state.js";
6
+ import { runStatus } from "../commands/status.js";
7
+ import { runContinuity } from "../commands/continuity.js";
8
+ import { listActions, readAction } from "./actions.js";
9
+ import { listApprovals } from "./approvals.js";
10
+ import { buildTrajectoryMetrics } from "./trajectory-metrics.js";
11
+ import { loadCapabilityPolicy } from "./capability-policy.js";
12
+ import { readdir } from "node:fs/promises";
13
+ import { readJsonArtifact } from "./artifacts.js";
14
+ import { taskDirectory } from "./task-paths.js";
15
+
16
+ /**
17
+ * Canonical integration resource allowlist.
18
+ *
19
+ * This is NOT a replacement for ARTIFACT_REGISTRY: the artifact registry
20
+ * describes persisted protocol artifacts; this registry describes what is
21
+ * safe to expose through structured integrations. Anything not listed here
22
+ * must never be readable through an integration transport.
23
+ *
24
+ * task/ownership is derived exclusively from resolveTaskClaimState() — the
25
+ * canonical claim resolver. Integrations must present these values; they
26
+ * must never derive them from raw artifacts such as task.json or
27
+ * recovery.json.
28
+ */
29
+ export const INTEGRATION_RESOURCE_DEFINITIONS = Object.freeze({
30
+ "protocol/info": Object.freeze({
31
+ scope: "PROJECT",
32
+ description: "ForgeLoop protocol version, schema compatibility, features, and command metadata.",
33
+ }),
34
+ "project/tasks": Object.freeze({
35
+ scope: "PROJECT",
36
+ description: "All discovered tasks with canonical ownership projection fields.",
37
+ }),
38
+ "task/status": Object.freeze({
39
+ scope: "TASK",
40
+ description: "Canonical status projection for one task, including ownership fields.",
41
+ }),
42
+ "task/ownership": Object.freeze({
43
+ scope: "TASK",
44
+ description: "Canonical validated claim ownership for one task.",
45
+ }),
46
+ "task/contract": Object.freeze({
47
+ scope: "TASK",
48
+ description: "The task's current contract.",
49
+ }),
50
+ "task/continuity": Object.freeze({
51
+ scope: "TASK",
52
+ description: "Cross-harness continuity state for one task.",
53
+ }),
54
+ "task/actions": Object.freeze({ scope: "TASK", description: "Canonical durable action summaries for one task." }),
55
+ "task/action": Object.freeze({ scope: "TASK", description: "One canonical durable action artifact." }),
56
+ "task/approvals": Object.freeze({ scope: "TASK", description: "Durable approval artifacts for one task." }),
57
+ "task/metrics": Object.freeze({ scope: "TASK", description: "Read-only trajectory metrics for one task." }),
58
+ "task/evaluations": Object.freeze({ scope: "TASK", description: "Persisted trajectory evaluations for one task." }),
59
+ "project/capability-policy": Object.freeze({ scope: "PROJECT", description: "Project capability policy, never host authority." }),
60
+ });
61
+
62
+ function ownershipProjection(projection) {
63
+ return {
64
+ taskId: projection.taskId,
65
+ phase: projection.phase,
66
+ claimState: projection.claimState,
67
+ mutationAllowed: projection.mutationAllowed,
68
+ ownershipValid: projection.ownershipValid,
69
+ recoveryStatus: projection.recoveryStatus,
70
+ historicalWriteClaims: [...projection.historicalWriteClaims],
71
+ effectiveWriteClaims: [...projection.effectiveWriteClaims],
72
+ reasonCodes: [...projection.reasonCodes],
73
+ };
74
+ }
75
+
76
+ export async function readForgeLoopIntegrationResource(uri, {
77
+ projectPath = ".",
78
+ packageRoot = undefined,
79
+ packageVersion = null,
80
+ taskId = null,
81
+ actionId = null,
82
+ } = {}) {
83
+ const resource = INTEGRATION_RESOURCE_DEFINITIONS[uri];
84
+ if (!resource) {
85
+ const error = new Error(`Unknown ForgeLoop integration resource: ${uri}`);
86
+ error.code = "E_INTEGRATION_RESOURCE_UNKNOWN";
87
+ throw error;
88
+ }
89
+
90
+ switch (uri) {
91
+ case "protocol/info": {
92
+ return { uri, data: await runProtocolInfo({ packageVersion }) };
93
+ }
94
+ case "project/tasks": {
95
+ const tasks = await discoverTasks(projectPath, packageRoot);
96
+ return {
97
+ uri,
98
+ data: {
99
+ count: tasks.length,
100
+ tasks: tasks.map((task) => ({
101
+ taskId: task.taskId,
102
+ healthy: task.healthy !== false,
103
+ phase: task.phase ?? null,
104
+ mutationAllowed: task.mutationAllowed !== false,
105
+ })),
106
+ },
107
+ };
108
+ }
109
+ case "task/status":
110
+ case "task/ownership":
111
+ case "task/contract":
112
+ case "task/continuity": {
113
+ if (typeof taskId !== "string" || !taskId) {
114
+ const error = new Error(`Resource ${uri} requires a taskId`);
115
+ error.code = "E_TASK_REQUIRED";
116
+ throw error;
117
+ }
118
+ break;
119
+ }
120
+ case "task/actions":
121
+ case "task/action":
122
+ case "task/approvals":
123
+ case "task/metrics":
124
+ case "task/evaluations": {
125
+ if (typeof taskId !== "string" || !taskId) {
126
+ const error = new Error(`Resource ${uri} requires a taskId`); error.code = "E_TASK_REQUIRED"; throw error;
127
+ }
128
+ break;
129
+ }
130
+ }
131
+
132
+ if (uri === "task/ownership") {
133
+ const projection = await resolveTaskClaimState(projectPath, { taskId, packageRoot });
134
+ return { uri, taskId, data: ownershipProjection(projection) };
135
+ }
136
+ if (uri === "task/actions") {
137
+ const actions = await listActions(projectPath, { packageRoot, taskId });
138
+ return { uri, taskId, data: { actions } };
139
+ }
140
+ if (uri === "task/action") {
141
+ if (!actionId) { const error = new Error("Resource task/action requires actionId"); error.code = "E_ACTION_INVALID"; throw error; }
142
+ return { uri, taskId, data: await readAction(projectPath, { packageRoot, taskId, actionId }) };
143
+ }
144
+ if (uri === "task/approvals") {
145
+ return { uri, taskId, data: { approvals: await listApprovals(projectPath, { packageRoot, taskId }) } };
146
+ }
147
+ if (uri === "task/metrics") {
148
+ return { uri, taskId, data: await buildTrajectoryMetrics({ target: projectPath, packageRoot, taskId }) };
149
+ }
150
+ if (uri === "task/evaluations") {
151
+ const dir = path.join(projectPath, taskDirectory(taskId), "evaluations");
152
+ let names = []; try { names = await readdir(dir); } catch { /* absent is an empty projection */ }
153
+ const evaluations = [];
154
+ for (const name of names.filter((entry) => /^eval-[A-Za-z0-9_-]+\.json$/.test(entry)).sort()) {
155
+ evaluations.push((await readJsonArtifact(projectPath, `${taskDirectory(taskId)}/evaluations/${name}`, "trajectory-evaluation", packageRoot)).value);
156
+ }
157
+ return { uri, taskId, data: { evaluations } };
158
+ }
159
+ if (uri === "project/capability-policy") {
160
+ return { uri, data: await loadCapabilityPolicy(projectPath, packageRoot) };
161
+ }
162
+ if (uri === "task/status") {
163
+ const result = await runStatus({ target: projectPath, packageRoot, taskId });
164
+ return { uri, taskId, data: result };
165
+ }
166
+ if (uri === "task/contract") {
167
+ try {
168
+ const contract = await readContract(projectPath, packageRoot, { taskId });
169
+ return { uri, taskId, data: contract.value };
170
+ } catch (error) {
171
+ error.message = `Resource task/contract unavailable: ${error.message}`;
172
+ throw error;
173
+ }
174
+ }
175
+ // task/continuity
176
+ const continuity = await runContinuity({ target: projectPath, packageRoot, taskId });
177
+ return { uri, taskId, data: continuity };
178
+ }
@@ -16,6 +16,9 @@ export const NEXT_ACTIONS = Object.freeze({
16
16
  RECORD_DIAGNOSIS: "RECORD_DIAGNOSIS",
17
17
  CORRECT: "CORRECT",
18
18
  CHANGE_STRATEGY: "CHANGE_STRATEGY",
19
+ REQUIRE_NEW_DIAGNOSTIC_INFORMATION: "REQUIRE_NEW_DIAGNOSTIC_INFORMATION",
20
+ INTRODUCE_NEW_OBSERVATION: "INTRODUCE_NEW_OBSERVATION",
21
+ RECORD_INTERVENTION: "RECORD_INTERVENTION",
19
22
  ENTER_REVIEWING: "ENTER_REVIEWING",
20
23
  RECORD_TERMINAL_RESULT: "RECORD_TERMINAL_RESULT",
21
24
  PREPARE_COMPLETION: "PREPARE_COMPLETION",
@@ -31,9 +34,74 @@ export const NEXT_ACTIONS = Object.freeze({
31
34
  REPAIR_POLICY: "REPAIR_POLICY",
32
35
  RESTORE_BASELINE: "RESTORE_BASELINE",
33
36
  CONTINUE_WITH_EXISTING_BASELINE: "CONTINUE_WITH_EXISTING_BASELINE",
37
+ RECONCILE_CLOSURE: "RECONCILE_CLOSURE",
38
+ RECONCILE_ACTION: "RECONCILE_ACTION",
39
+ AUTHORIZE_ACTION: "AUTHORIZE_ACTION",
40
+ REQUEST_ACTION_APPROVAL: "REQUEST_ACTION_APPROVAL",
41
+ RESOLVE_ACTION_APPROVAL: "RESOLVE_ACTION_APPROVAL",
42
+ VERIFY_EXTERNAL_ACTION: "VERIFY_EXTERNAL_ACTION",
43
+ RECOVER_TASK: "RECOVER_TASK",
44
+ RESUME_RECOVERED_TASK: "RESUME_RECOVERED_TASK",
45
+ RESOLVE_RECOVERY_INCONSISTENCY: "RESOLVE_RECOVERY_INCONSISTENCY",
34
46
  NONE: "NONE",
35
47
  });
36
48
 
49
+ export function directCommandSpec(commandId, taskId, requiredInputs = []) {
50
+ return {
51
+ commandId,
52
+ executable: "forgeloop",
53
+ subcommand: commandId,
54
+ argv: [commandId, `--task=${taskId}`, "--json"],
55
+ requiredInputs,
56
+ };
57
+ }
58
+
59
+ export function recoveryGuidanceForClassification(classification, taskId) {
60
+ if (classification === "RECOVERABLE") {
61
+ return {
62
+ nextAction: NEXT_ACTIONS.RECONCILE_CLOSURE,
63
+ commands: ["forgeloop reconcile-closure --task <id>"],
64
+ commandSpecs: [directCommandSpec("reconcile-closure", taskId, [
65
+ { name: "checkId", option: "--id=<contract-verification-id>" },
66
+ { name: "requirement", option: "--requirement=<exact-contract-verification-text>" },
67
+ { name: "command", option: "-- <verification-command...>" },
68
+ ])],
69
+ };
70
+ }
71
+ if (classification === "STALE" || classification === "ABANDONED") {
72
+ return {
73
+ nextAction: NEXT_ACTIONS.RECOVER_TASK,
74
+ commands: ["forgeloop task-recover --task <id> --acknowledge-recovery --json"],
75
+ commandSpecs: [directCommandSpec("task-recover", taskId, [
76
+ {
77
+ name: "acknowledgeRecovery",
78
+ option: "--acknowledge-recovery",
79
+ description: "Caller acknowledgement only; not host-attested authority.",
80
+ },
81
+ ])],
82
+ };
83
+ }
84
+ if (classification === "RECOVERED") {
85
+ return {
86
+ nextAction: NEXT_ACTIONS.RESUME_RECOVERED_TASK,
87
+ commands: ["forgeloop task-resume --task <id> --json"],
88
+ commandSpecs: [directCommandSpec("task-resume", taskId)],
89
+ };
90
+ }
91
+ if (classification === "INCONSISTENT") {
92
+ return {
93
+ nextAction: NEXT_ACTIONS.RESOLVE_RECOVERY_INCONSISTENCY,
94
+ commands: ["forgeloop validate-protocol --task <id> --json"],
95
+ commandSpecs: [directCommandSpec("validate-protocol", taskId)],
96
+ };
97
+ }
98
+ return {
99
+ nextAction: NEXT_ACTIONS.RESOLVE_BLOCKER,
100
+ commands: ["forgeloop task-show --task <id> --json"],
101
+ commandSpecs: [directCommandSpec("task-show", taskId)],
102
+ };
103
+ }
104
+
37
105
  export function result({
38
106
  taskId = "unknown",
39
107
  currentPhase = "RECEIVED",
@@ -44,6 +112,12 @@ export function result({
44
112
  requiredArtifacts = [],
45
113
  missingArtifacts = [],
46
114
  progress = undefined,
115
+ diagnosticGuidance = undefined,
116
+ authorityRequired = undefined,
117
+ hostActionRequired = undefined,
118
+ approvalRequired = undefined,
119
+ capabilityDecision = undefined,
120
+ reconciliationAuthorityRequired = undefined,
47
121
  }) {
48
122
  const normalizedReasons = reasons
49
123
  .map((reason) => {
@@ -76,6 +150,14 @@ export function result({
76
150
  requiredArtifacts: uniqueSorted(requiredArtifacts),
77
151
  missingArtifacts: uniqueSorted(missingArtifacts),
78
152
  ...(progress ? { progress: structuredClone(progress) } : {}),
153
+ ...(diagnosticGuidance ? { diagnosticGuidance: structuredClone(diagnosticGuidance) } : {}),
154
+ ...(authorityRequired ? { authorityRequired: structuredClone(authorityRequired) } : {}),
155
+ ...(hostActionRequired ? { hostActionRequired: structuredClone(hostActionRequired) } : {}),
156
+ ...(approvalRequired ? { approvalRequired: structuredClone(approvalRequired) } : {}),
157
+ ...(capabilityDecision ? { capabilityDecision: structuredClone(capabilityDecision) } : {}),
158
+ ...(reconciliationAuthorityRequired
159
+ ? { reconciliationAuthorityRequired: structuredClone(reconciliationAuthorityRequired) }
160
+ : {}),
79
161
  };
80
162
  }
81
163
 
@@ -126,6 +208,18 @@ export function recordDiagnosisCommandSpec() {
126
208
  };
127
209
  }
128
210
 
211
+ export function recordInterventionCommandSpec(taskId = null) {
212
+ return {
213
+ commandId: "record-intervention",
214
+ executable: "forgeloop",
215
+ subcommand: "record-intervention",
216
+ argv: ["record-intervention", ...(taskId ? [`--task=${taskId}`] : [])],
217
+ requiredInputs: [
218
+ { name: "file", option: "--file=<intervention-json-path>" },
219
+ ],
220
+ };
221
+ }
222
+
129
223
  export function recordTerminalResultCommandSpec(requirement) {
130
224
  const reqId = requirement.id ?? requirement;
131
225
  const type = requirement.type ?? "PUBLICATION";