kibi-mcp 0.16.0 → 0.17.2
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.
- package/dist/semantic-advisor/analyze-prose.js +1367 -0
- package/dist/semantic-advisor/prose-coverage-evaluator.js +66 -0
- package/dist/semantic-advisor/types.js +1 -0
- package/dist/server/kb-freshness.js +88 -0
- package/dist/server/session.js +65 -4
- package/dist/server/tools.js +16 -0
- package/dist/tools/check.js +2 -0
- package/dist/tools/model-requirement.js +10 -0
- package/dist/tools/semantic-advisor.js +42 -0
- package/dist/tools/sparql.js +96 -0
- package/dist/tools/suggest-predicates.js +1905 -9
- package/dist/tools/upsert.js +354 -85
- package/dist/tools/validate-upsert.js +43 -0
- package/dist/tools-config.js +109 -7
- package/package.json +7 -4
|
@@ -1,5 +1,471 @@
|
|
|
1
1
|
import { createHash } from "node:crypto";
|
|
2
2
|
import { parseEntityFromList, parseListOfLists } from "kibi-cli/prolog/codec";
|
|
3
|
+
const DEFAULT_USAGE_HINTS = {
|
|
4
|
+
use_when: [
|
|
5
|
+
"Use when the prose matches this predicate signature and all required arguments can be named explicitly.",
|
|
6
|
+
],
|
|
7
|
+
do_not_use_when: [
|
|
8
|
+
"Do not use when a stricter scalar property, a more specific predicate, or an ontology-gap observation better preserves the claim.",
|
|
9
|
+
],
|
|
10
|
+
};
|
|
11
|
+
const USAGE_HINTS_BY_PREDICATE = {
|
|
12
|
+
state: {
|
|
13
|
+
use_when: [
|
|
14
|
+
"Use for one subject being in, entering, or requiring one named state.",
|
|
15
|
+
],
|
|
16
|
+
do_not_use_when: [
|
|
17
|
+
"Do not use for allowed state sets or explicit state-to-state movement; use state_membership or state_transition instead.",
|
|
18
|
+
],
|
|
19
|
+
},
|
|
20
|
+
transition: {
|
|
21
|
+
use_when: [
|
|
22
|
+
"Use for workflow prose where a subject moves between states because of a trigger.",
|
|
23
|
+
],
|
|
24
|
+
do_not_use_when: [
|
|
25
|
+
"Do not use for one current state, allowed terminal states, or non-state conditional behavior.",
|
|
26
|
+
],
|
|
27
|
+
},
|
|
28
|
+
guard: {
|
|
29
|
+
use_when: [
|
|
30
|
+
"Use when a boolean condition gates whether behavior is active or permitted.",
|
|
31
|
+
],
|
|
32
|
+
do_not_use_when: [
|
|
33
|
+
"Do not use for explicit unless/except exceptions, actor authorization, feature flags, or scalar thresholds.",
|
|
34
|
+
],
|
|
35
|
+
},
|
|
36
|
+
exception_rule: {
|
|
37
|
+
use_when: [
|
|
38
|
+
"Use for unless/except clauses where a normally required behavior is skipped under a named exception.",
|
|
39
|
+
],
|
|
40
|
+
do_not_use_when: [
|
|
41
|
+
"Do not use for generic boolean guards without an exception clause, actor permissions, or feature flags.",
|
|
42
|
+
],
|
|
43
|
+
},
|
|
44
|
+
mutual_exclusion: {
|
|
45
|
+
use_when: [
|
|
46
|
+
"Use when two modes, states, options, or behaviors cannot be true or active together.",
|
|
47
|
+
],
|
|
48
|
+
do_not_use_when: [
|
|
49
|
+
"Do not use for uniqueness per scope, allowed state sets, or ordinary permission denials.",
|
|
50
|
+
],
|
|
51
|
+
},
|
|
52
|
+
dependency_rule: {
|
|
53
|
+
use_when: [
|
|
54
|
+
"Use when one action, state, or resource requires another prerequisite before it can proceed.",
|
|
55
|
+
],
|
|
56
|
+
do_not_use_when: [
|
|
57
|
+
"Do not use for temporal ordering without a required prerequisite, ownership, or feature gates.",
|
|
58
|
+
],
|
|
59
|
+
},
|
|
60
|
+
ownership_rule: {
|
|
61
|
+
use_when: [
|
|
62
|
+
"Use when a resource, domain area, or behavior is owned by a service, team, role, or component.",
|
|
63
|
+
],
|
|
64
|
+
do_not_use_when: [
|
|
65
|
+
"Do not use for permission to access a resource or event publication.",
|
|
66
|
+
],
|
|
67
|
+
},
|
|
68
|
+
retry_policy: {
|
|
69
|
+
use_when: [
|
|
70
|
+
"Use when a failed action must be retried a bounded number of times or attempts.",
|
|
71
|
+
],
|
|
72
|
+
do_not_use_when: [
|
|
73
|
+
"Do not use for rate limits, backoff timing without retries, or escalation after a delay.",
|
|
74
|
+
],
|
|
75
|
+
},
|
|
76
|
+
escalation_rule: {
|
|
77
|
+
use_when: [
|
|
78
|
+
"Use when an unresolved or failed condition is escalated to an owner, role, queue, or service after a delay.",
|
|
79
|
+
],
|
|
80
|
+
do_not_use_when: [
|
|
81
|
+
"Do not use for ownership assignments, retry counts, or simple notification routing.",
|
|
82
|
+
],
|
|
83
|
+
},
|
|
84
|
+
availability_sla: {
|
|
85
|
+
use_when: [
|
|
86
|
+
"Use when an API, service, or system has a minimum availability target over a reporting window.",
|
|
87
|
+
],
|
|
88
|
+
do_not_use_when: [
|
|
89
|
+
"Do not use for latency thresholds, retention durations, or generic resource constraints.",
|
|
90
|
+
],
|
|
91
|
+
},
|
|
92
|
+
notification_route: {
|
|
93
|
+
use_when: [
|
|
94
|
+
"Use when a subject must notify a recipient, team, or role through a specific channel.",
|
|
95
|
+
],
|
|
96
|
+
do_not_use_when: [
|
|
97
|
+
"Do not use for event publication, ownership assignment, or escalation after a delay.",
|
|
98
|
+
],
|
|
99
|
+
},
|
|
100
|
+
idempotency_rule: {
|
|
101
|
+
use_when: [
|
|
102
|
+
"Use when repeated, redundant, or concurrent requests or operations must be safely deduplicated by an idempotency key or equivalent identity.",
|
|
103
|
+
],
|
|
104
|
+
do_not_use_when: [
|
|
105
|
+
"Do not use for general uniqueness constraints, retry counts, or rate limits; choose the more specific predicate instead.",
|
|
106
|
+
],
|
|
107
|
+
},
|
|
108
|
+
data_residency_rule: {
|
|
109
|
+
use_when: [
|
|
110
|
+
"Use when data must be stored, processed, or kept within a named region or jurisdiction.",
|
|
111
|
+
],
|
|
112
|
+
do_not_use_when: [
|
|
113
|
+
"Do not use for retention durations, access permissions, or notification routing.",
|
|
114
|
+
],
|
|
115
|
+
},
|
|
116
|
+
audit_event_rule: {
|
|
117
|
+
use_when: [
|
|
118
|
+
"Use when an action, change, or security-relevant event must be recorded in an audit log or audit trail.",
|
|
119
|
+
],
|
|
120
|
+
do_not_use_when: [
|
|
121
|
+
"Do not use for user-facing notifications, event publication, or generic dirty-change state.",
|
|
122
|
+
],
|
|
123
|
+
},
|
|
124
|
+
consent_rule: {
|
|
125
|
+
use_when: [
|
|
126
|
+
"Use when an action or data-processing purpose requires explicit user consent before it proceeds.",
|
|
127
|
+
],
|
|
128
|
+
do_not_use_when: [
|
|
129
|
+
"Do not use for data residency, permission denials, or retention durations.",
|
|
130
|
+
],
|
|
131
|
+
},
|
|
132
|
+
lifecycle_rule: {
|
|
133
|
+
use_when: [
|
|
134
|
+
"Use when an entity must be archived, deleted, expired, or otherwise lifecycle-transitioned after a duration.",
|
|
135
|
+
],
|
|
136
|
+
do_not_use_when: [
|
|
137
|
+
"Do not use for passive retention duration requirements; use retention_policy when data is simply kept for a period.",
|
|
138
|
+
],
|
|
139
|
+
},
|
|
140
|
+
conflict_resolution_rule: {
|
|
141
|
+
use_when: [
|
|
142
|
+
"Use when synchronization or concurrent updates require a named conflict-resolution strategy.",
|
|
143
|
+
],
|
|
144
|
+
do_not_use_when: [
|
|
145
|
+
"Do not use for ordinary state transitions, dependency ordering, or fallback behavior.",
|
|
146
|
+
],
|
|
147
|
+
},
|
|
148
|
+
fallback_rule: {
|
|
149
|
+
use_when: [
|
|
150
|
+
"Use when a degraded or unavailable dependency causes a subject to fall back to a named alternative behavior.",
|
|
151
|
+
],
|
|
152
|
+
do_not_use_when: [
|
|
153
|
+
"Do not use for escalation after a delay, notification routing, or retry policies.",
|
|
154
|
+
],
|
|
155
|
+
},
|
|
156
|
+
batch_operation_rule: {
|
|
157
|
+
use_when: [
|
|
158
|
+
"Use when a bulk operation must process a resource in batches of a specific size.",
|
|
159
|
+
],
|
|
160
|
+
do_not_use_when: [
|
|
161
|
+
"Do not use for rate limits, retry counts, or general resource thresholds.",
|
|
162
|
+
],
|
|
163
|
+
},
|
|
164
|
+
consistency_rule: {
|
|
165
|
+
use_when: [
|
|
166
|
+
"Use when one entity must reference, match, or remain consistent with another existing entity or invariant.",
|
|
167
|
+
],
|
|
168
|
+
do_not_use_when: [
|
|
169
|
+
"Do not use for ownership, permission checks, or prerequisite workflow dependencies.",
|
|
170
|
+
],
|
|
171
|
+
},
|
|
172
|
+
build_constraint: {
|
|
173
|
+
use_when: [
|
|
174
|
+
"Use when build-time generation or deployment configuration must satisfy a deterministic property.",
|
|
175
|
+
],
|
|
176
|
+
do_not_use_when: [
|
|
177
|
+
"Do not use for runtime feature gates or ordinary resource thresholds.",
|
|
178
|
+
],
|
|
179
|
+
},
|
|
180
|
+
environment_safety_rule: {
|
|
181
|
+
use_when: [
|
|
182
|
+
"Use when an action is allowed or forbidden in a named environment such as production or staging.",
|
|
183
|
+
],
|
|
184
|
+
do_not_use_when: [
|
|
185
|
+
"Do not use for actor permissions that are independent of deployment environment.",
|
|
186
|
+
],
|
|
187
|
+
},
|
|
188
|
+
schema_invariant_rule: {
|
|
189
|
+
use_when: [
|
|
190
|
+
"Use when a schema field has an invariant such as immutability, type, enum, or value-range constraints.",
|
|
191
|
+
],
|
|
192
|
+
do_not_use_when: [
|
|
193
|
+
"Do not use for cross-entity references; use consistency_rule for referential integrity.",
|
|
194
|
+
],
|
|
195
|
+
},
|
|
196
|
+
coding_standard_rule: {
|
|
197
|
+
use_when: [
|
|
198
|
+
"Use when developer-facing code must use or avoid a framework API, pattern, or documentation practice.",
|
|
199
|
+
],
|
|
200
|
+
do_not_use_when: [
|
|
201
|
+
"Do not use for user-facing product behavior or role-based access control.",
|
|
202
|
+
],
|
|
203
|
+
},
|
|
204
|
+
migration_boundary_rule: {
|
|
205
|
+
use_when: [
|
|
206
|
+
"Use when legacy data or APIs may only be used for migration or compatibility input.",
|
|
207
|
+
],
|
|
208
|
+
do_not_use_when: [
|
|
209
|
+
"Do not use for general lifecycle archive/delete timing rules.",
|
|
210
|
+
],
|
|
211
|
+
},
|
|
212
|
+
absence_requirement: {
|
|
213
|
+
use_when: [
|
|
214
|
+
"Use when a component, extension, RPC, schema, or feature must be absent, removed, or not exist.",
|
|
215
|
+
],
|
|
216
|
+
do_not_use_when: [
|
|
217
|
+
"Do not use for denied user actions; use permission_rule for actor/action/resource denials.",
|
|
218
|
+
],
|
|
219
|
+
},
|
|
220
|
+
offline_behavior_rule: {
|
|
221
|
+
use_when: [
|
|
222
|
+
"Use when synchronization or gameplay behavior must remain non-blocking or resilient during offline conditions.",
|
|
223
|
+
],
|
|
224
|
+
do_not_use_when: [
|
|
225
|
+
"Do not use for generic fallback behavior without an offline/resilient synchronization condition.",
|
|
226
|
+
],
|
|
227
|
+
},
|
|
228
|
+
release_gate_rule: {
|
|
229
|
+
use_when: [
|
|
230
|
+
"Use when builds or releases must pass named gates before distribution, review, or deployment.",
|
|
231
|
+
],
|
|
232
|
+
do_not_use_when: [
|
|
233
|
+
"Do not use for runtime dependency prerequisites; use dependency_rule for product workflow dependencies.",
|
|
234
|
+
],
|
|
235
|
+
},
|
|
236
|
+
platform_consistency_rule: {
|
|
237
|
+
use_when: [
|
|
238
|
+
"Use when a state or entitlement must synchronize across named platforms.",
|
|
239
|
+
],
|
|
240
|
+
do_not_use_when: [
|
|
241
|
+
"Do not use for referential integrity within one data model; use consistency_rule instead.",
|
|
242
|
+
],
|
|
243
|
+
},
|
|
244
|
+
preservation_rule: {
|
|
245
|
+
use_when: [
|
|
246
|
+
"Use when child data or feedback must be preserved across deletion/removal of a parent resource.",
|
|
247
|
+
],
|
|
248
|
+
do_not_use_when: [
|
|
249
|
+
"Do not use for simple archive/delete timing rules without a preservation target.",
|
|
250
|
+
],
|
|
251
|
+
},
|
|
252
|
+
abstraction_boundary_rule: {
|
|
253
|
+
use_when: [
|
|
254
|
+
"Use when persisted data, APIs, or contracts must remain neutral to a renderer, vendor, runtime, or implementation detail.",
|
|
255
|
+
],
|
|
256
|
+
do_not_use_when: [
|
|
257
|
+
"Do not use for generic coding standards without a boundary/contract target; use coding_standard_rule instead.",
|
|
258
|
+
],
|
|
259
|
+
},
|
|
260
|
+
security_configuration_rule: {
|
|
261
|
+
use_when: [
|
|
262
|
+
"Use when infrastructure, database, trigger, RPC, or deployment components must carry an explicit security configuration value.",
|
|
263
|
+
],
|
|
264
|
+
do_not_use_when: [
|
|
265
|
+
"Do not use for actor authorization decisions; use permission_rule or scoped_authorization_rule instead.",
|
|
266
|
+
],
|
|
267
|
+
},
|
|
268
|
+
ordered_strategy_rule: {
|
|
269
|
+
use_when: [
|
|
270
|
+
"Use when a subject must apply named strategies in a required priority or fallback order.",
|
|
271
|
+
],
|
|
272
|
+
do_not_use_when: [
|
|
273
|
+
"Do not use for unordered enum membership; use strict allowed_values or state_membership instead.",
|
|
274
|
+
],
|
|
275
|
+
},
|
|
276
|
+
refresh_policy_rule: {
|
|
277
|
+
use_when: [
|
|
278
|
+
"Use when a UI, dashboard, cache, or list must refresh a target automatically or on a named cadence/trigger.",
|
|
279
|
+
],
|
|
280
|
+
do_not_use_when: [
|
|
281
|
+
"Do not use for save/commit actions or notification routing; use commit_action or notification_route instead.",
|
|
282
|
+
],
|
|
283
|
+
},
|
|
284
|
+
scoped_authorization_rule: {
|
|
285
|
+
use_when: [
|
|
286
|
+
"Use when an actor is allowed or denied an action because they are assigned, unassigned, owner, member, or otherwise scoped to a resource.",
|
|
287
|
+
],
|
|
288
|
+
do_not_use_when: [
|
|
289
|
+
"Do not use for simple actor/action/resource permissions without a scope qualifier; use permission_rule instead.",
|
|
290
|
+
],
|
|
291
|
+
},
|
|
292
|
+
documentation_standard_rule: {
|
|
293
|
+
use_when: [
|
|
294
|
+
"Use when code symbols, APIs, tests, or project artifacts must be documented in a named documentation source.",
|
|
295
|
+
],
|
|
296
|
+
do_not_use_when: [
|
|
297
|
+
"Do not use for runtime persistence or user-facing display labels.",
|
|
298
|
+
],
|
|
299
|
+
},
|
|
300
|
+
warmup_policy_rule: {
|
|
301
|
+
use_when: [
|
|
302
|
+
"Use when a component, media element, cache, or service must warm up on a named entry or trigger.",
|
|
303
|
+
],
|
|
304
|
+
do_not_use_when: [
|
|
305
|
+
"Do not use for generic state transitions without warmup behavior.",
|
|
306
|
+
],
|
|
307
|
+
},
|
|
308
|
+
visual_layout_rule: {
|
|
309
|
+
use_when: [
|
|
310
|
+
"Use when UI elements must remain visually aligned, consistent, or layout-compatible with another element.",
|
|
311
|
+
],
|
|
312
|
+
do_not_use_when: [
|
|
313
|
+
"Do not use for generic acceptance outcomes without a visual/layout relationship.",
|
|
314
|
+
],
|
|
315
|
+
},
|
|
316
|
+
enforcement_location_rule: {
|
|
317
|
+
use_when: [
|
|
318
|
+
"Use when a constraint or policy must be enforced at a named layer such as database, application, edge, or client level.",
|
|
319
|
+
],
|
|
320
|
+
do_not_use_when: [
|
|
321
|
+
"Do not use for the constraint itself when the enforcement location is not stated.",
|
|
322
|
+
],
|
|
323
|
+
},
|
|
324
|
+
reconciliation_rule: {
|
|
325
|
+
use_when: [
|
|
326
|
+
"Use when a trigger requires reconciling a target and clearing stale or inconsistent records.",
|
|
327
|
+
],
|
|
328
|
+
do_not_use_when: [
|
|
329
|
+
"Do not use for ordinary archive/delete timing without reconciliation.",
|
|
330
|
+
],
|
|
331
|
+
},
|
|
332
|
+
throttle_policy_rule: {
|
|
333
|
+
use_when: [
|
|
334
|
+
"Use when handlers, operations, or streams must be throttled for high-frequency activity.",
|
|
335
|
+
],
|
|
336
|
+
do_not_use_when: [
|
|
337
|
+
"Do not use for numeric API request quotas; use rate_limit instead.",
|
|
338
|
+
],
|
|
339
|
+
},
|
|
340
|
+
has_unsaved_changes: {
|
|
341
|
+
use_when: [
|
|
342
|
+
"Use for dirty, draft, or unsaved local-edit state on a subject.",
|
|
343
|
+
],
|
|
344
|
+
do_not_use_when: [
|
|
345
|
+
"Do not use for the action that saves or discards those changes.",
|
|
346
|
+
],
|
|
347
|
+
},
|
|
348
|
+
commit_action: {
|
|
349
|
+
use_when: [
|
|
350
|
+
"Use when a trigger saves, commits, persists, or auto-saves a subject in a scope.",
|
|
351
|
+
],
|
|
352
|
+
do_not_use_when: [
|
|
353
|
+
"Do not use for cancel, discard, revert, or abandon behavior.",
|
|
354
|
+
],
|
|
355
|
+
},
|
|
356
|
+
discard_action: {
|
|
357
|
+
use_when: [
|
|
358
|
+
"Use when a trigger discards, cancels, reverts, or abandons changes in a scope.",
|
|
359
|
+
],
|
|
360
|
+
do_not_use_when: ["Do not use for persistence or save behavior."],
|
|
361
|
+
},
|
|
362
|
+
accessibility_requirement: {
|
|
363
|
+
use_when: [
|
|
364
|
+
"Use for WCAG, keyboard, screen-reader, or accessibility target requirements.",
|
|
365
|
+
],
|
|
366
|
+
do_not_use_when: [
|
|
367
|
+
"Do not use for generic acceptance outcomes or performance constraints.",
|
|
368
|
+
],
|
|
369
|
+
},
|
|
370
|
+
retention_policy: {
|
|
371
|
+
use_when: [
|
|
372
|
+
"Use when records or data must be retained for a bounded duration.",
|
|
373
|
+
],
|
|
374
|
+
do_not_use_when: [
|
|
375
|
+
"Do not use for request rate windows, session expiry, or latency thresholds.",
|
|
376
|
+
],
|
|
377
|
+
},
|
|
378
|
+
resource_constraint: {
|
|
379
|
+
use_when: [
|
|
380
|
+
"Use for resource, latency, timeout, size, quota, or numeric threshold constraints.",
|
|
381
|
+
],
|
|
382
|
+
do_not_use_when: [
|
|
383
|
+
"Do not use for uniqueness, rate limits, feature gates, or actor permissions.",
|
|
384
|
+
],
|
|
385
|
+
},
|
|
386
|
+
feature_gate: {
|
|
387
|
+
use_when: [
|
|
388
|
+
"Use when behavior is controlled by a runtime/config flag, kill switch, or feature gate.",
|
|
389
|
+
],
|
|
390
|
+
do_not_use_when: [
|
|
391
|
+
"Do not use for actor authorization or ordinary scalar enabled/disabled properties.",
|
|
392
|
+
],
|
|
393
|
+
},
|
|
394
|
+
publishes_event: {
|
|
395
|
+
use_when: [
|
|
396
|
+
"Use when a subject publishes, emits, or raises a domain/system event.",
|
|
397
|
+
],
|
|
398
|
+
do_not_use_when: [
|
|
399
|
+
"Do not use for consuming events or observable UI acceptance outcomes.",
|
|
400
|
+
],
|
|
401
|
+
},
|
|
402
|
+
acceptance_rule: {
|
|
403
|
+
use_when: [
|
|
404
|
+
"Use for observable acceptance outcomes such as visible empty states or displayed results.",
|
|
405
|
+
],
|
|
406
|
+
do_not_use_when: [
|
|
407
|
+
"Do not use for internal state machines, event publication, or accessibility standards.",
|
|
408
|
+
],
|
|
409
|
+
},
|
|
410
|
+
permission_rule: {
|
|
411
|
+
use_when: [
|
|
412
|
+
"Use for allow/deny permission statements with an actor, action, and resource.",
|
|
413
|
+
],
|
|
414
|
+
do_not_use_when: [
|
|
415
|
+
"Do not use for scalar quotas, feature flags, or state transitions; choose a more specific predicate or strict property instead.",
|
|
416
|
+
],
|
|
417
|
+
},
|
|
418
|
+
default_value: {
|
|
419
|
+
use_when: [
|
|
420
|
+
"Use when behavior defines a default mode, state, status, option, or value.",
|
|
421
|
+
],
|
|
422
|
+
do_not_use_when: [
|
|
423
|
+
"Do not use for current-state assertions or allowed value sets.",
|
|
424
|
+
],
|
|
425
|
+
},
|
|
426
|
+
uniqueness_constraint: {
|
|
427
|
+
use_when: ["Use for at-most-one or unique-per-scope constraints."],
|
|
428
|
+
do_not_use_when: [
|
|
429
|
+
"Do not use for numeric caps where more than one instance may be valid.",
|
|
430
|
+
],
|
|
431
|
+
},
|
|
432
|
+
state_membership: {
|
|
433
|
+
use_when: ["Use for terminal, allowed, or enumerated state sets."],
|
|
434
|
+
do_not_use_when: [
|
|
435
|
+
"Do not use for one current state or a transition between states.",
|
|
436
|
+
],
|
|
437
|
+
},
|
|
438
|
+
temporal_order: {
|
|
439
|
+
use_when: [
|
|
440
|
+
"Use for before/after ordering requirements between events, states, or actions.",
|
|
441
|
+
],
|
|
442
|
+
do_not_use_when: [
|
|
443
|
+
"Do not use when source state, target state, and trigger are all explicit; use state_transition instead.",
|
|
444
|
+
],
|
|
445
|
+
},
|
|
446
|
+
conditional_behavior: {
|
|
447
|
+
use_when: [
|
|
448
|
+
"Use for if/when conditional prose where a condition leads to a behavior.",
|
|
449
|
+
],
|
|
450
|
+
do_not_use_when: [
|
|
451
|
+
"Do not use for simple boolean guards or actor permission rules.",
|
|
452
|
+
],
|
|
453
|
+
},
|
|
454
|
+
state_transition: {
|
|
455
|
+
use_when: [
|
|
456
|
+
"Use for explicit transitions from one named state to another with a trigger.",
|
|
457
|
+
],
|
|
458
|
+
do_not_use_when: [
|
|
459
|
+
"Do not use for a single state assertion or an allowed terminal-state set.",
|
|
460
|
+
],
|
|
461
|
+
},
|
|
462
|
+
rate_limit: {
|
|
463
|
+
use_when: ["Use for per-window request, attempt, or action limits."],
|
|
464
|
+
do_not_use_when: [
|
|
465
|
+
"Do not use for resource thresholds that lack a repeated action and time window.",
|
|
466
|
+
],
|
|
467
|
+
},
|
|
468
|
+
};
|
|
3
469
|
const DEFAULT_MIN_SCORE = 0.35;
|
|
4
470
|
const DEFAULT_MAX_CANDIDATES = 5;
|
|
5
471
|
const BUILT_IN_PREDICATE_SCHEMAS = [
|
|
@@ -27,6 +493,7 @@ const BUILT_IN_PREDICATE_SCHEMAS = [
|
|
|
27
493
|
"leave",
|
|
28
494
|
"idle",
|
|
29
495
|
"navigate",
|
|
496
|
+
"navigates",
|
|
30
497
|
"cancel",
|
|
31
498
|
"escape",
|
|
32
499
|
],
|
|
@@ -40,10 +507,472 @@ const BUILT_IN_PREDICATE_SCHEMAS = [
|
|
|
40
507
|
description: "A condition gates or forbids behavior for a subject.",
|
|
41
508
|
argument_names: ["subject", "condition", "expected"],
|
|
42
509
|
argument_types: ["entity", "condition", "boolean"],
|
|
43
|
-
keywords: ["guard", "unless", "readonly", "scrubbing"],
|
|
510
|
+
keywords: ["guard", "unless", "readonly", "scrubbing", "disabled until"],
|
|
44
511
|
examples: ["guard(editor.annotation, isReadOnly, false)"],
|
|
45
512
|
tags: ["guard", "workflow"],
|
|
46
513
|
},
|
|
514
|
+
{
|
|
515
|
+
id: "FACT-SCHEMA-EXCEPTION-RULE",
|
|
516
|
+
predicate_name: "exception_rule",
|
|
517
|
+
title: "Exception rule",
|
|
518
|
+
description: "A required behavior is skipped when a named exception condition holds.",
|
|
519
|
+
argument_names: ["subject", "behavior", "exception"],
|
|
520
|
+
argument_types: ["entity", "behavior", "condition"],
|
|
521
|
+
keywords: ["unless", "except", "except when", "opted out", "exception"],
|
|
522
|
+
examples: [
|
|
523
|
+
"exception_rule(notification.service, send_email, user_has_opted_out)",
|
|
524
|
+
],
|
|
525
|
+
tags: ["exception", "conditional"],
|
|
526
|
+
},
|
|
527
|
+
{
|
|
528
|
+
id: "FACT-SCHEMA-MUTUAL-EXCLUSION",
|
|
529
|
+
predicate_name: "mutual_exclusion",
|
|
530
|
+
title: "Mutual exclusion",
|
|
531
|
+
description: "Two modes, states, options, or behaviors cannot be true or active together.",
|
|
532
|
+
argument_names: ["left", "right"],
|
|
533
|
+
argument_types: ["entity", "entity"],
|
|
534
|
+
keywords: ["mutually exclusive", "exclusive", "cannot overlap", "not both"],
|
|
535
|
+
examples: ["mutual_exclusion(practice_mode, exam_mode)"],
|
|
536
|
+
tags: ["constraint", "exclusivity"],
|
|
537
|
+
},
|
|
538
|
+
{
|
|
539
|
+
id: "FACT-SCHEMA-DEPENDENCY-RULE",
|
|
540
|
+
predicate_name: "dependency_rule",
|
|
541
|
+
title: "Dependency rule",
|
|
542
|
+
description: "A subject requires a prerequisite before a dependent action or state can proceed.",
|
|
543
|
+
argument_names: ["subject", "prerequisite", "dependent"],
|
|
544
|
+
argument_types: ["entity", "condition", "action"],
|
|
545
|
+
keywords: ["requires", "depends on", "prerequisite", "before"],
|
|
546
|
+
examples: [
|
|
547
|
+
"dependency_rule(checkout, payment_authorization, order_submission)",
|
|
548
|
+
],
|
|
549
|
+
tags: ["dependency", "workflow"],
|
|
550
|
+
},
|
|
551
|
+
{
|
|
552
|
+
id: "FACT-SCHEMA-OWNERSHIP-RULE",
|
|
553
|
+
predicate_name: "ownership_rule",
|
|
554
|
+
title: "Ownership rule",
|
|
555
|
+
description: "A resource or behavior is owned by a service, team, role, or component.",
|
|
556
|
+
argument_names: ["resource", "owner"],
|
|
557
|
+
argument_types: ["entity", "owner"],
|
|
558
|
+
keywords: ["owned by", "owner", "responsible for"],
|
|
559
|
+
examples: ["ownership_rule(account_settings, profile_service)"],
|
|
560
|
+
tags: ["ownership", "governance"],
|
|
561
|
+
},
|
|
562
|
+
{
|
|
563
|
+
id: "FACT-SCHEMA-RETRY-POLICY",
|
|
564
|
+
predicate_name: "retry_policy",
|
|
565
|
+
title: "Retry policy",
|
|
566
|
+
description: "A failed subject or action retries up to a bounded attempt count.",
|
|
567
|
+
argument_names: ["subject", "count", "unit"],
|
|
568
|
+
argument_types: ["entity", "number", "unit"],
|
|
569
|
+
keywords: ["retry", "retries", "up to", "attempts", "times"],
|
|
570
|
+
examples: ["retry_policy(failed_webhook_delivery, 3, times)"],
|
|
571
|
+
tags: ["retry", "resilience"],
|
|
572
|
+
},
|
|
573
|
+
{
|
|
574
|
+
id: "FACT-SCHEMA-ESCALATION-RULE",
|
|
575
|
+
predicate_name: "escalation_rule",
|
|
576
|
+
title: "Escalation rule",
|
|
577
|
+
description: "A failed or unresolved subject escalates to an owner after a bounded delay.",
|
|
578
|
+
argument_names: ["subject", "target", "delay", "unit"],
|
|
579
|
+
argument_types: ["entity", "owner", "number", "unit"],
|
|
580
|
+
keywords: ["escalate", "escalates", "escalation", "after", "support"],
|
|
581
|
+
examples: ["escalation_rule(failed_payment_disputes, support, 48, hours)"],
|
|
582
|
+
tags: ["escalation", "operations"],
|
|
583
|
+
},
|
|
584
|
+
{
|
|
585
|
+
id: "FACT-SCHEMA-AVAILABILITY-SLA",
|
|
586
|
+
predicate_name: "availability_sla",
|
|
587
|
+
title: "Availability SLA",
|
|
588
|
+
description: "A service or API must meet a minimum availability target over a reporting window.",
|
|
589
|
+
argument_names: ["subject", "threshold", "unit", "window"],
|
|
590
|
+
argument_types: ["entity", "number", "unit", "duration"],
|
|
591
|
+
keywords: ["availability", "at least", "percent", "monthly", "sla"],
|
|
592
|
+
examples: ["availability_sla(checkout_api, 99.9, percent, monthly)"],
|
|
593
|
+
tags: ["sla", "availability"],
|
|
594
|
+
},
|
|
595
|
+
{
|
|
596
|
+
id: "FACT-SCHEMA-NOTIFICATION-ROUTE",
|
|
597
|
+
predicate_name: "notification_route",
|
|
598
|
+
title: "Notification route",
|
|
599
|
+
description: "A subject notifies a target recipient through a specific channel.",
|
|
600
|
+
argument_names: ["subject", "recipient", "channel"],
|
|
601
|
+
argument_types: ["entity", "recipient", "channel"],
|
|
602
|
+
keywords: ["notify", "notifies", "notification", "by email", "by sms"],
|
|
603
|
+
examples: ["notification_route(fraud_alerts, risk_operations, email)"],
|
|
604
|
+
tags: ["notification", "routing"],
|
|
605
|
+
},
|
|
606
|
+
{
|
|
607
|
+
id: "FACT-SCHEMA-IDEMPOTENCY-RULE",
|
|
608
|
+
predicate_name: "idempotency_rule",
|
|
609
|
+
title: "Idempotency rule",
|
|
610
|
+
description: "A request or operation must deduplicate repeated execution by a stable idempotency key.",
|
|
611
|
+
argument_names: ["subject", "key"],
|
|
612
|
+
argument_types: ["entity", "key"],
|
|
613
|
+
keywords: [
|
|
614
|
+
"idempotent",
|
|
615
|
+
"idempotency",
|
|
616
|
+
"idempotency key",
|
|
617
|
+
"dedupe",
|
|
618
|
+
"deduplicated",
|
|
619
|
+
"redundant",
|
|
620
|
+
"concurrent",
|
|
621
|
+
],
|
|
622
|
+
examples: ["idempotency_rule(payment_capture_requests, idempotency_key)"],
|
|
623
|
+
tags: ["idempotency", "resilience"],
|
|
624
|
+
},
|
|
625
|
+
{
|
|
626
|
+
id: "FACT-SCHEMA-DATA-RESIDENCY-RULE",
|
|
627
|
+
predicate_name: "data_residency_rule",
|
|
628
|
+
title: "Data residency rule",
|
|
629
|
+
description: "A data set must be stored, processed, or kept within a named region or jurisdiction.",
|
|
630
|
+
argument_names: ["subject", "region"],
|
|
631
|
+
argument_types: ["entity", "region"],
|
|
632
|
+
keywords: ["data residency", "stored in", "region", "jurisdiction"],
|
|
633
|
+
examples: ["data_residency_rule(eu_customer_data, eu_region)"],
|
|
634
|
+
tags: ["privacy", "data-residency"],
|
|
635
|
+
},
|
|
636
|
+
{
|
|
637
|
+
id: "FACT-SCHEMA-AUDIT-EVENT-RULE",
|
|
638
|
+
predicate_name: "audit_event_rule",
|
|
639
|
+
title: "Audit event rule",
|
|
640
|
+
description: "An action, change, or security-relevant event must be recorded in an audit log or audit trail.",
|
|
641
|
+
argument_names: ["subject", "log"],
|
|
642
|
+
argument_types: ["entity", "log"],
|
|
643
|
+
keywords: ["audit log", "audit trail", "recorded", "logged", "audited"],
|
|
644
|
+
examples: ["audit_event_rule(admin_access_changes, audit_log)"],
|
|
645
|
+
tags: ["audit", "logging", "governance"],
|
|
646
|
+
},
|
|
647
|
+
{
|
|
648
|
+
id: "FACT-SCHEMA-CONSENT-RULE",
|
|
649
|
+
predicate_name: "consent_rule",
|
|
650
|
+
title: "Consent rule",
|
|
651
|
+
description: "An action or processing purpose requires a named consent before it proceeds.",
|
|
652
|
+
argument_names: ["subject", "consent", "purpose"],
|
|
653
|
+
argument_types: ["entity", "consent", "purpose"],
|
|
654
|
+
keywords: ["consent", "requires consent", "before processing"],
|
|
655
|
+
examples: ["consent_rule(marketing_emails, user_consent, processing)"],
|
|
656
|
+
tags: ["privacy", "consent"],
|
|
657
|
+
},
|
|
658
|
+
{
|
|
659
|
+
id: "FACT-SCHEMA-LIFECYCLE-RULE",
|
|
660
|
+
predicate_name: "lifecycle_rule",
|
|
661
|
+
title: "Lifecycle rule",
|
|
662
|
+
description: "An entity must be archived, deleted, expired, or lifecycle-transitioned after a duration.",
|
|
663
|
+
argument_names: ["subject", "action", "duration", "unit"],
|
|
664
|
+
argument_types: ["entity", "action", "number", "unit"],
|
|
665
|
+
keywords: ["archive", "archived", "delete", "deleted", "expire", "after"],
|
|
666
|
+
examples: ["lifecycle_rule(expired_sessions, archived, 30, days)"],
|
|
667
|
+
tags: ["lifecycle", "retention"],
|
|
668
|
+
},
|
|
669
|
+
{
|
|
670
|
+
id: "FACT-SCHEMA-CONFLICT-RESOLUTION-RULE",
|
|
671
|
+
predicate_name: "conflict_resolution_rule",
|
|
672
|
+
title: "Conflict resolution rule",
|
|
673
|
+
description: "Concurrent or synchronized updates resolve conflicts with a named strategy.",
|
|
674
|
+
argument_names: ["subject", "strategy"],
|
|
675
|
+
argument_types: ["entity", "strategy"],
|
|
676
|
+
keywords: ["conflict", "conflicts", "latest write wins", "merge"],
|
|
677
|
+
examples: ["conflict_resolution_rule(profile_updates, latest_write_wins)"],
|
|
678
|
+
tags: ["sync", "conflict-resolution"],
|
|
679
|
+
},
|
|
680
|
+
{
|
|
681
|
+
id: "FACT-SCHEMA-FALLBACK-RULE",
|
|
682
|
+
predicate_name: "fallback_rule",
|
|
683
|
+
title: "Fallback rule",
|
|
684
|
+
description: "A degraded or unavailable dependency causes a subject to fall back to an alternative behavior.",
|
|
685
|
+
argument_names: ["condition", "subject", "fallback"],
|
|
686
|
+
argument_types: ["condition", "entity", "behavior"],
|
|
687
|
+
keywords: ["fall back", "fallback", "unavailable", "degraded"],
|
|
688
|
+
examples: [
|
|
689
|
+
"fallback_rule(payment_provider_is_unavailable, checkout, manual_review)",
|
|
690
|
+
],
|
|
691
|
+
tags: ["fallback", "resilience"],
|
|
692
|
+
},
|
|
693
|
+
{
|
|
694
|
+
id: "FACT-SCHEMA-BATCH-OPERATION-RULE",
|
|
695
|
+
predicate_name: "batch_operation_rule",
|
|
696
|
+
title: "Batch operation rule",
|
|
697
|
+
description: "A bulk operation processes a resource in batches of a bounded size.",
|
|
698
|
+
argument_names: ["subject", "resource", "batch_size"],
|
|
699
|
+
argument_types: ["entity", "resource", "number"],
|
|
700
|
+
keywords: ["batch", "batches", "bulk", "process"],
|
|
701
|
+
examples: ["batch_operation_rule(invoice_exports, records, 500)"],
|
|
702
|
+
tags: ["batching", "bulk"],
|
|
703
|
+
},
|
|
704
|
+
{
|
|
705
|
+
id: "FACT-SCHEMA-CONSISTENCY-RULE",
|
|
706
|
+
predicate_name: "consistency_rule",
|
|
707
|
+
title: "Consistency rule",
|
|
708
|
+
description: "An entity reference or value must remain consistent with another existing entity or invariant.",
|
|
709
|
+
argument_names: ["subject", "target"],
|
|
710
|
+
argument_types: ["entity", "entity"],
|
|
711
|
+
keywords: ["reference", "references", "existing", "consistent"],
|
|
712
|
+
examples: ["consistency_rule(order_items, existing_order)"],
|
|
713
|
+
tags: ["consistency", "referential-integrity"],
|
|
714
|
+
},
|
|
715
|
+
{
|
|
716
|
+
id: "FACT-SCHEMA-BUILD-CONSTRAINT",
|
|
717
|
+
predicate_name: "build_constraint",
|
|
718
|
+
title: "Build constraint",
|
|
719
|
+
description: "Build-time generation or deployment configuration must satisfy a deterministic property.",
|
|
720
|
+
argument_names: ["subject", "property", "scope"],
|
|
721
|
+
argument_types: ["entity", "property", "scope"],
|
|
722
|
+
keywords: ["build time", "deterministic", "generator", "deployment"],
|
|
723
|
+
examples: [
|
|
724
|
+
"build_constraint(share_manifest_generation, deterministic, build_time)",
|
|
725
|
+
],
|
|
726
|
+
tags: ["build", "determinism"],
|
|
727
|
+
},
|
|
728
|
+
{
|
|
729
|
+
id: "FACT-SCHEMA-ENVIRONMENT-SAFETY-RULE",
|
|
730
|
+
predicate_name: "environment_safety_rule",
|
|
731
|
+
title: "Environment safety rule",
|
|
732
|
+
description: "A named action is allowed or forbidden in a deployment environment.",
|
|
733
|
+
argument_names: ["action", "decision", "environment"],
|
|
734
|
+
argument_types: ["action", "decision", "environment"],
|
|
735
|
+
keywords: ["production", "staging", "forbidden", "destructive"],
|
|
736
|
+
examples: [
|
|
737
|
+
"environment_safety_rule(destructive_operations, forbidden, production)",
|
|
738
|
+
],
|
|
739
|
+
tags: ["environment", "safety"],
|
|
740
|
+
},
|
|
741
|
+
{
|
|
742
|
+
id: "FACT-SCHEMA-SCHEMA-INVARIANT-RULE",
|
|
743
|
+
predicate_name: "schema_invariant_rule",
|
|
744
|
+
title: "Schema invariant rule",
|
|
745
|
+
description: "A schema field has an invariant such as immutability, type, enum, or value range.",
|
|
746
|
+
argument_names: ["field", "invariant", "scope"],
|
|
747
|
+
argument_types: ["field", "invariant", "scope"],
|
|
748
|
+
keywords: ["immutable", "schema", "field", "enum", "range"],
|
|
749
|
+
examples: ["schema_invariant_rule(user_email, immutable, after_creation)"],
|
|
750
|
+
tags: ["schema", "invariant"],
|
|
751
|
+
},
|
|
752
|
+
{
|
|
753
|
+
id: "FACT-SCHEMA-CODING-STANDARD-RULE",
|
|
754
|
+
predicate_name: "coding_standard_rule",
|
|
755
|
+
title: "Coding standard rule",
|
|
756
|
+
description: "Developer-facing code must use or avoid a framework API, pattern, or documentation practice.",
|
|
757
|
+
argument_names: ["subject", "action", "target"],
|
|
758
|
+
argument_types: ["entity", "action", "api"],
|
|
759
|
+
keywords: ["computed", "signals", "must use", "must not use"],
|
|
760
|
+
examples: ["coding_standard_rule(derived_state, use, computed_signals)"],
|
|
761
|
+
tags: ["coding-standard", "agent-guidance"],
|
|
762
|
+
},
|
|
763
|
+
{
|
|
764
|
+
id: "FACT-SCHEMA-MIGRATION-BOUNDARY-RULE",
|
|
765
|
+
predicate_name: "migration_boundary_rule",
|
|
766
|
+
title: "Migration boundary rule",
|
|
767
|
+
description: "Legacy data or APIs may only be used for migration or compatibility input.",
|
|
768
|
+
argument_names: ["subject", "allowed_action", "scope"],
|
|
769
|
+
argument_types: ["entity", "action", "scope"],
|
|
770
|
+
keywords: ["legacy", "migration input", "only be read"],
|
|
771
|
+
examples: [
|
|
772
|
+
"migration_boundary_rule(legacy_fabricdata, read, migration_input)",
|
|
773
|
+
],
|
|
774
|
+
tags: ["migration", "compatibility"],
|
|
775
|
+
},
|
|
776
|
+
{
|
|
777
|
+
id: "FACT-SCHEMA-ABSENCE-REQUIREMENT",
|
|
778
|
+
predicate_name: "absence_requirement",
|
|
779
|
+
title: "Absence requirement",
|
|
780
|
+
description: "A component, extension, RPC, schema, or feature must be absent, removed, or not exist.",
|
|
781
|
+
argument_names: ["subject", "state"],
|
|
782
|
+
argument_types: ["entity", "state"],
|
|
783
|
+
keywords: ["absent", "removed", "must not exist", "not exist", "no"],
|
|
784
|
+
examples: ["absence_requirement(pg_graphql_extension, absent)"],
|
|
785
|
+
tags: ["absence", "security"],
|
|
786
|
+
},
|
|
787
|
+
{
|
|
788
|
+
id: "FACT-SCHEMA-OFFLINE-BEHAVIOR-RULE",
|
|
789
|
+
predicate_name: "offline_behavior_rule",
|
|
790
|
+
title: "Offline behavior rule",
|
|
791
|
+
description: "Synchronization or gameplay behavior remains non-blocking or resilient during offline conditions.",
|
|
792
|
+
argument_names: ["subject", "behavior", "condition"],
|
|
793
|
+
argument_types: ["entity", "behavior", "condition"],
|
|
794
|
+
keywords: ["offline", "non-blocking", "resilient", "synchronization"],
|
|
795
|
+
examples: [
|
|
796
|
+
"offline_behavior_rule(cloud_synchronization, non-blocking, offline_conditions)",
|
|
797
|
+
],
|
|
798
|
+
tags: ["offline", "sync"],
|
|
799
|
+
},
|
|
800
|
+
{
|
|
801
|
+
id: "FACT-SCHEMA-RELEASE-GATE-RULE",
|
|
802
|
+
predicate_name: "release_gate_rule",
|
|
803
|
+
title: "Release gate rule",
|
|
804
|
+
description: "A build or release must pass named gates before distribution, review, or deployment.",
|
|
805
|
+
argument_names: ["subject", "gate", "target"],
|
|
806
|
+
argument_types: ["entity", "gate", "release_target"],
|
|
807
|
+
keywords: ["release", "gates", "before", "testflight", "distribution"],
|
|
808
|
+
examples: [
|
|
809
|
+
"release_gate_rule(ios_builds, configuration_gates, testflight_distribution)",
|
|
810
|
+
],
|
|
811
|
+
tags: ["release", "gate"],
|
|
812
|
+
},
|
|
813
|
+
{
|
|
814
|
+
id: "FACT-SCHEMA-PLATFORM-CONSISTENCY-RULE",
|
|
815
|
+
predicate_name: "platform_consistency_rule",
|
|
816
|
+
title: "Platform consistency rule",
|
|
817
|
+
description: "A state or entitlement synchronizes across named platforms.",
|
|
818
|
+
argument_names: ["subject", "platforms"],
|
|
819
|
+
argument_types: ["entity", "platform_list"],
|
|
820
|
+
keywords: ["synchronize", "across", "platforms", "ios", "android", "web"],
|
|
821
|
+
examples: ["platform_consistency_rule(premium_status, ios,android,web)"],
|
|
822
|
+
tags: ["platform", "consistency"],
|
|
823
|
+
},
|
|
824
|
+
{
|
|
825
|
+
id: "FACT-SCHEMA-PRESERVATION-RULE",
|
|
826
|
+
predicate_name: "preservation_rule",
|
|
827
|
+
title: "Preservation rule",
|
|
828
|
+
description: "Child data or feedback is preserved across deletion or removal of a parent resource.",
|
|
829
|
+
argument_names: ["subject", "preserved", "condition"],
|
|
830
|
+
argument_types: ["action", "entity", "condition"],
|
|
831
|
+
keywords: ["preserve", "preserved", "deletion", "removed"],
|
|
832
|
+
examples: [
|
|
833
|
+
"preservation_rule(soft_deletion, annotations, video_is_removed)",
|
|
834
|
+
],
|
|
835
|
+
tags: ["preservation", "deletion"],
|
|
836
|
+
},
|
|
837
|
+
{
|
|
838
|
+
id: "FACT-SCHEMA-ABSTRACTION-BOUNDARY-RULE",
|
|
839
|
+
predicate_name: "abstraction_boundary_rule",
|
|
840
|
+
title: "Abstraction boundary rule",
|
|
841
|
+
description: "Persisted data, APIs, or contracts must stay neutral to a renderer, vendor, runtime, or implementation detail.",
|
|
842
|
+
argument_names: ["subject", "relation", "contract"],
|
|
843
|
+
argument_types: ["entity", "relation", "contract"],
|
|
844
|
+
keywords: ["renderer-neutral", "contract", "runtime snapshots", "vendor"],
|
|
845
|
+
examples: [
|
|
846
|
+
"abstraction_boundary_rule(annotation_drawing_data, persisted_as, renderer-neutral_scene_contract)",
|
|
847
|
+
],
|
|
848
|
+
tags: ["architecture", "abstraction"],
|
|
849
|
+
},
|
|
850
|
+
{
|
|
851
|
+
id: "FACT-SCHEMA-SECURITY-CONFIGURATION-RULE",
|
|
852
|
+
predicate_name: "security_configuration_rule",
|
|
853
|
+
title: "Security configuration rule",
|
|
854
|
+
description: "A security-sensitive component must have an explicit configuration value.",
|
|
855
|
+
argument_names: ["subject", "setting", "value"],
|
|
856
|
+
argument_types: ["entity", "setting", "value"],
|
|
857
|
+
keywords: ["explicit", "search_path", "security", "configuration"],
|
|
858
|
+
examples: [
|
|
859
|
+
"security_configuration_rule(trigger_functions, search_path, public)",
|
|
860
|
+
],
|
|
861
|
+
tags: ["security", "configuration"],
|
|
862
|
+
},
|
|
863
|
+
{
|
|
864
|
+
id: "FACT-SCHEMA-ORDERED-STRATEGY-RULE",
|
|
865
|
+
predicate_name: "ordered_strategy_rule",
|
|
866
|
+
title: "Ordered strategy rule",
|
|
867
|
+
description: "A subject must use strategies in a required priority order.",
|
|
868
|
+
argument_names: ["subject", "strategy_kind", "ordered_values"],
|
|
869
|
+
argument_types: ["entity", "strategy_kind", "ordered_list"],
|
|
870
|
+
keywords: ["priority order", "selector strategies", "fallback order"],
|
|
871
|
+
examples: [
|
|
872
|
+
"ordered_strategy_rule(poms, selector_strategies, data-testid,visible,text)",
|
|
873
|
+
],
|
|
874
|
+
tags: ["strategy", "priority"],
|
|
875
|
+
},
|
|
876
|
+
{
|
|
877
|
+
id: "FACT-SCHEMA-REFRESH-POLICY-RULE",
|
|
878
|
+
predicate_name: "refresh_policy_rule",
|
|
879
|
+
title: "Refresh policy rule",
|
|
880
|
+
description: "A subject refreshes a target automatically or by a named policy.",
|
|
881
|
+
argument_names: ["subject", "target", "policy"],
|
|
882
|
+
argument_types: ["entity", "target", "policy"],
|
|
883
|
+
keywords: ["automatically refresh", "manual page reload", "refresh"],
|
|
884
|
+
examples: ["refresh_policy_rule(dashboard, processing_videos, automatic)"],
|
|
885
|
+
tags: ["refresh", "ui"],
|
|
886
|
+
},
|
|
887
|
+
{
|
|
888
|
+
id: "FACT-SCHEMA-SCOPED-AUTHORIZATION-RULE",
|
|
889
|
+
predicate_name: "scoped_authorization_rule",
|
|
890
|
+
title: "Scoped authorization rule",
|
|
891
|
+
description: "A scoped actor is allowed or denied an action because of assignment, ownership, or membership.",
|
|
892
|
+
argument_names: ["actor_scope", "action", "decision"],
|
|
893
|
+
argument_types: ["actor_scope", "action", "decision"],
|
|
894
|
+
keywords: ["unassigned", "assigned", "denied", "scoped"],
|
|
895
|
+
examples: [
|
|
896
|
+
"scoped_authorization_rule(unassigned_instructors, signed_url_generation, deny)",
|
|
897
|
+
],
|
|
898
|
+
tags: ["authorization", "scope"],
|
|
899
|
+
},
|
|
900
|
+
{
|
|
901
|
+
id: "FACT-SCHEMA-DOCUMENTATION-STANDARD-RULE",
|
|
902
|
+
predicate_name: "documentation_standard_rule",
|
|
903
|
+
title: "Documentation standard rule",
|
|
904
|
+
description: "A subject must be documented in a named documentation artifact.",
|
|
905
|
+
argument_names: ["subject", "relation", "artifact"],
|
|
906
|
+
argument_types: ["entity", "relation", "artifact"],
|
|
907
|
+
keywords: ["documented", "docs", "symbols", "documentation"],
|
|
908
|
+
examples: [
|
|
909
|
+
"documentation_standard_rule(code_symbols, documented_in, docs_symbols_yaml)",
|
|
910
|
+
],
|
|
911
|
+
tags: ["documentation", "standard"],
|
|
912
|
+
},
|
|
913
|
+
{
|
|
914
|
+
id: "FACT-SCHEMA-WARMUP-POLICY-RULE",
|
|
915
|
+
predicate_name: "warmup_policy_rule",
|
|
916
|
+
title: "Warmup policy rule",
|
|
917
|
+
description: "A subject must warm up on a named trigger or entry point.",
|
|
918
|
+
argument_names: ["subject", "trigger"],
|
|
919
|
+
argument_types: ["entity", "trigger"],
|
|
920
|
+
keywords: ["warm up", "warmup", "on entry"],
|
|
921
|
+
examples: ["warmup_policy_rule(editor_video, entry)"],
|
|
922
|
+
tags: ["warmup", "performance"],
|
|
923
|
+
},
|
|
924
|
+
{
|
|
925
|
+
id: "FACT-SCHEMA-VISUAL-LAYOUT-RULE",
|
|
926
|
+
predicate_name: "visual_layout_rule",
|
|
927
|
+
title: "Visual layout rule",
|
|
928
|
+
description: "A UI subject must remain visually aligned or layout-consistent with a target.",
|
|
929
|
+
argument_names: ["subject", "relation", "target"],
|
|
930
|
+
argument_types: ["ui_entity", "relation", "ui_entity"],
|
|
931
|
+
keywords: ["visually aligned", "layout", "aligned with"],
|
|
932
|
+
examples: [
|
|
933
|
+
"visual_layout_rule(processing_video_cards, aligned_with, ready_cards)",
|
|
934
|
+
],
|
|
935
|
+
tags: ["visual", "layout"],
|
|
936
|
+
},
|
|
937
|
+
{
|
|
938
|
+
id: "FACT-SCHEMA-ENFORCEMENT-LOCATION-RULE",
|
|
939
|
+
predicate_name: "enforcement_location_rule",
|
|
940
|
+
title: "Enforcement location rule",
|
|
941
|
+
description: "A constraint or policy must be enforced at a named system layer.",
|
|
942
|
+
argument_names: ["subject", "location"],
|
|
943
|
+
argument_types: ["constraint", "system_layer"],
|
|
944
|
+
keywords: ["enforced at", "database level", "application level"],
|
|
945
|
+
examples: [
|
|
946
|
+
"enforcement_location_rule(foreign_key_constraints, database_level)",
|
|
947
|
+
],
|
|
948
|
+
tags: ["enforcement", "architecture"],
|
|
949
|
+
},
|
|
950
|
+
{
|
|
951
|
+
id: "FACT-SCHEMA-RECONCILIATION-RULE",
|
|
952
|
+
predicate_name: "reconciliation_rule",
|
|
953
|
+
title: "Reconciliation rule",
|
|
954
|
+
description: "A trigger reconciles a target and performs a stale-data cleanup action.",
|
|
955
|
+
argument_names: ["subject", "trigger", "target", "action"],
|
|
956
|
+
argument_types: ["entity", "trigger", "target", "action"],
|
|
957
|
+
keywords: ["reconcile", "clear stale", "stale notifications"],
|
|
958
|
+
examples: [
|
|
959
|
+
"reconciliation_rule(system, login, analysis_notifications, clear_stale_notifications)",
|
|
960
|
+
],
|
|
961
|
+
tags: ["reconciliation", "cleanup"],
|
|
962
|
+
},
|
|
963
|
+
{
|
|
964
|
+
id: "FACT-SCHEMA-THROTTLE-POLICY-RULE",
|
|
965
|
+
predicate_name: "throttle_policy_rule",
|
|
966
|
+
title: "Throttle policy rule",
|
|
967
|
+
description: "Handlers or operations must be throttled for a named activity class.",
|
|
968
|
+
argument_names: ["subject", "condition"],
|
|
969
|
+
argument_types: ["entity", "condition"],
|
|
970
|
+
keywords: ["throttled", "high-frequency", "event handlers"],
|
|
971
|
+
examples: [
|
|
972
|
+
"throttle_policy_rule(event_handlers, high-frequency_operations)",
|
|
973
|
+
],
|
|
974
|
+
tags: ["throttle", "performance"],
|
|
975
|
+
},
|
|
47
976
|
{
|
|
48
977
|
id: "FACT-SCHEMA-HAS-UNSAVED-CHANGES",
|
|
49
978
|
predicate_name: "has_unsaved_changes",
|
|
@@ -157,8 +1086,115 @@ const BUILT_IN_PREDICATE_SCHEMAS = [
|
|
|
157
1086
|
"must show",
|
|
158
1087
|
"must display",
|
|
159
1088
|
],
|
|
160
|
-
examples: ["acceptance_rule(search.results, shows_empty_state)"],
|
|
161
|
-
tags: ["acceptance", "quality"],
|
|
1089
|
+
examples: ["acceptance_rule(search.results, shows_empty_state)"],
|
|
1090
|
+
tags: ["acceptance", "quality"],
|
|
1091
|
+
},
|
|
1092
|
+
{
|
|
1093
|
+
id: "FACT-SCHEMA-PERMISSION-RULE",
|
|
1094
|
+
predicate_name: "permission_rule",
|
|
1095
|
+
title: "Permission rule",
|
|
1096
|
+
description: "An actor is allowed or denied an action against a resource.",
|
|
1097
|
+
argument_names: ["actor", "action", "resource", "decision"],
|
|
1098
|
+
argument_types: ["actor", "action", "resource", "decision"],
|
|
1099
|
+
keywords: [
|
|
1100
|
+
"may",
|
|
1101
|
+
"can",
|
|
1102
|
+
"allowed",
|
|
1103
|
+
"denied",
|
|
1104
|
+
"forbidden",
|
|
1105
|
+
"must not",
|
|
1106
|
+
"cannot",
|
|
1107
|
+
"can't",
|
|
1108
|
+
"export",
|
|
1109
|
+
"permission",
|
|
1110
|
+
],
|
|
1111
|
+
examples: ["permission_rule(guest, export, customer_data, deny)"],
|
|
1112
|
+
tags: ["permission", "policy"],
|
|
1113
|
+
},
|
|
1114
|
+
{
|
|
1115
|
+
id: "FACT-SCHEMA-DEFAULT-VALUE",
|
|
1116
|
+
predicate_name: "default_value",
|
|
1117
|
+
title: "Default value",
|
|
1118
|
+
description: "A subject property defaults to a value.",
|
|
1119
|
+
argument_names: ["subject", "property", "value"],
|
|
1120
|
+
argument_types: ["entity", "property", "value"],
|
|
1121
|
+
keywords: ["default", "defaults", "defaulted", "initial", "mode", "tool"],
|
|
1122
|
+
examples: ["default_value(annotation.tool, mode, move)"],
|
|
1123
|
+
tags: ["default", "configuration"],
|
|
1124
|
+
},
|
|
1125
|
+
{
|
|
1126
|
+
id: "FACT-SCHEMA-UNIQUENESS-CONSTRAINT",
|
|
1127
|
+
predicate_name: "uniqueness_constraint",
|
|
1128
|
+
title: "Uniqueness constraint",
|
|
1129
|
+
description: "A subject is unique within one or more scope keys.",
|
|
1130
|
+
argument_names: ["subject", "scope"],
|
|
1131
|
+
argument_types: ["entity", "scope"],
|
|
1132
|
+
keywords: ["unique", "uniqueness", "at most one", "one", "per"],
|
|
1133
|
+
examples: ["uniqueness_constraint(annotation, video,timeKey)"],
|
|
1134
|
+
tags: ["constraint", "identity"],
|
|
1135
|
+
},
|
|
1136
|
+
{
|
|
1137
|
+
id: "FACT-SCHEMA-STATE-MEMBERSHIP",
|
|
1138
|
+
predicate_name: "state_membership",
|
|
1139
|
+
title: "State membership",
|
|
1140
|
+
description: "A state-valued subject has a closed set of allowed states.",
|
|
1141
|
+
argument_names: ["subject", "states"],
|
|
1142
|
+
argument_types: ["entity", "state_list"],
|
|
1143
|
+
keywords: ["terminal states", "states are", "one of", "ready", "anonymous"],
|
|
1144
|
+
examples: ["state_membership(auth.status, ready,anonymous,profile-error)"],
|
|
1145
|
+
tags: ["state", "workflow"],
|
|
1146
|
+
},
|
|
1147
|
+
{
|
|
1148
|
+
id: "FACT-SCHEMA-TEMPORAL-ORDER",
|
|
1149
|
+
predicate_name: "temporal_order",
|
|
1150
|
+
title: "Temporal order",
|
|
1151
|
+
description: "One event or state must occur before or after another.",
|
|
1152
|
+
argument_names: ["subject", "before_event", "after_event"],
|
|
1153
|
+
argument_types: ["entity", "event", "event"],
|
|
1154
|
+
keywords: ["before", "after", "saved", "completes", "navigation"],
|
|
1155
|
+
examples: ["temporal_order(draft.changes, saved, navigation_completes)"],
|
|
1156
|
+
tags: ["temporal", "workflow"],
|
|
1157
|
+
},
|
|
1158
|
+
{
|
|
1159
|
+
id: "FACT-SCHEMA-CONDITIONAL-BEHAVIOR",
|
|
1160
|
+
predicate_name: "conditional_behavior",
|
|
1161
|
+
title: "Conditional behavior",
|
|
1162
|
+
description: "A behavior follows when a condition is true.",
|
|
1163
|
+
argument_names: ["subject", "condition", "behavior"],
|
|
1164
|
+
argument_types: ["entity", "condition", "behavior"],
|
|
1165
|
+
keywords: ["if", "during", "becomes", "condition", "provided that"],
|
|
1166
|
+
examples: [
|
|
1167
|
+
"conditional_behavior(card, fails_during_session, becomes_tainted)",
|
|
1168
|
+
],
|
|
1169
|
+
tags: ["conditional", "workflow"],
|
|
1170
|
+
},
|
|
1171
|
+
{
|
|
1172
|
+
id: "FACT-SCHEMA-STATE-TRANSITION",
|
|
1173
|
+
predicate_name: "state_transition",
|
|
1174
|
+
title: "State transition",
|
|
1175
|
+
description: "A subject moves from one state to another because of a trigger.",
|
|
1176
|
+
argument_names: ["subject", "from_state", "to_state", "trigger"],
|
|
1177
|
+
argument_types: ["entity", "state", "state", "trigger"],
|
|
1178
|
+
keywords: ["transitions from", "transition from", "from draft", "to idle"],
|
|
1179
|
+
examples: ["state_transition(editor, draft, idle, navigation_completes)"],
|
|
1180
|
+
tags: ["state", "workflow"],
|
|
1181
|
+
},
|
|
1182
|
+
{
|
|
1183
|
+
id: "FACT-SCHEMA-RATE-LIMIT",
|
|
1184
|
+
predicate_name: "rate_limit",
|
|
1185
|
+
title: "Rate limit",
|
|
1186
|
+
description: "An action is limited to a count within a time window.",
|
|
1187
|
+
argument_names: ["subject", "action", "window", "count"],
|
|
1188
|
+
argument_types: ["entity", "action", "duration", "number"],
|
|
1189
|
+
keywords: [
|
|
1190
|
+
"rate limited",
|
|
1191
|
+
"rate limit",
|
|
1192
|
+
"attempts",
|
|
1193
|
+
"per hour",
|
|
1194
|
+
"per minute",
|
|
1195
|
+
],
|
|
1196
|
+
examples: ["rate_limit(password_reset.request, attempts, hour, 5)"],
|
|
1197
|
+
tags: ["rate-limit", "security"],
|
|
162
1198
|
},
|
|
163
1199
|
];
|
|
164
1200
|
function normalizeText(text) {
|
|
@@ -249,11 +1285,83 @@ function inferArgs(schema, text, subject) {
|
|
|
249
1285
|
inferTrigger(text),
|
|
250
1286
|
];
|
|
251
1287
|
case "guard":
|
|
252
|
-
return
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
1288
|
+
return inferGuardArgs(text, subject);
|
|
1289
|
+
case "exception_rule":
|
|
1290
|
+
return inferExceptionRuleArgs(text, subject);
|
|
1291
|
+
case "mutual_exclusion":
|
|
1292
|
+
return inferMutualExclusionArgs(text);
|
|
1293
|
+
case "dependency_rule":
|
|
1294
|
+
return inferDependencyRuleArgs(text);
|
|
1295
|
+
case "ownership_rule":
|
|
1296
|
+
return inferOwnershipRuleArgs(text);
|
|
1297
|
+
case "retry_policy":
|
|
1298
|
+
return inferRetryPolicyArgs(text);
|
|
1299
|
+
case "escalation_rule":
|
|
1300
|
+
return inferEscalationRuleArgs(text);
|
|
1301
|
+
case "availability_sla":
|
|
1302
|
+
return inferAvailabilitySlaArgs(text);
|
|
1303
|
+
case "notification_route":
|
|
1304
|
+
return inferNotificationRouteArgs(text);
|
|
1305
|
+
case "idempotency_rule":
|
|
1306
|
+
return inferIdempotencyRuleArgs(text);
|
|
1307
|
+
case "data_residency_rule":
|
|
1308
|
+
return inferDataResidencyRuleArgs(text);
|
|
1309
|
+
case "audit_event_rule":
|
|
1310
|
+
return inferAuditEventRuleArgs(text);
|
|
1311
|
+
case "consent_rule":
|
|
1312
|
+
return inferConsentRuleArgs(text);
|
|
1313
|
+
case "lifecycle_rule":
|
|
1314
|
+
return inferLifecycleRuleArgs(text);
|
|
1315
|
+
case "conflict_resolution_rule":
|
|
1316
|
+
return inferConflictResolutionRuleArgs(text);
|
|
1317
|
+
case "fallback_rule":
|
|
1318
|
+
return inferFallbackRuleArgs(text);
|
|
1319
|
+
case "batch_operation_rule":
|
|
1320
|
+
return inferBatchOperationRuleArgs(text);
|
|
1321
|
+
case "consistency_rule":
|
|
1322
|
+
return inferConsistencyRuleArgs(text);
|
|
1323
|
+
case "build_constraint":
|
|
1324
|
+
return inferBuildConstraintArgs(text);
|
|
1325
|
+
case "environment_safety_rule":
|
|
1326
|
+
return inferEnvironmentSafetyRuleArgs(text);
|
|
1327
|
+
case "schema_invariant_rule":
|
|
1328
|
+
return inferSchemaInvariantRuleArgs(text);
|
|
1329
|
+
case "coding_standard_rule":
|
|
1330
|
+
return inferCodingStandardRuleArgs(text);
|
|
1331
|
+
case "migration_boundary_rule":
|
|
1332
|
+
return inferMigrationBoundaryRuleArgs(text);
|
|
1333
|
+
case "absence_requirement":
|
|
1334
|
+
return inferAbsenceRequirementArgs(text);
|
|
1335
|
+
case "offline_behavior_rule":
|
|
1336
|
+
return inferOfflineBehaviorRuleArgs(text);
|
|
1337
|
+
case "release_gate_rule":
|
|
1338
|
+
return inferReleaseGateRuleArgs(text);
|
|
1339
|
+
case "platform_consistency_rule":
|
|
1340
|
+
return inferPlatformConsistencyRuleArgs(text);
|
|
1341
|
+
case "preservation_rule":
|
|
1342
|
+
return inferPreservationRuleArgs(text);
|
|
1343
|
+
case "abstraction_boundary_rule":
|
|
1344
|
+
return inferAbstractionBoundaryRuleArgs(text);
|
|
1345
|
+
case "security_configuration_rule":
|
|
1346
|
+
return inferSecurityConfigurationRuleArgs(text);
|
|
1347
|
+
case "ordered_strategy_rule":
|
|
1348
|
+
return inferOrderedStrategyRuleArgs(text);
|
|
1349
|
+
case "refresh_policy_rule":
|
|
1350
|
+
return inferRefreshPolicyRuleArgs(text);
|
|
1351
|
+
case "scoped_authorization_rule":
|
|
1352
|
+
return inferScopedAuthorizationRuleArgs(text);
|
|
1353
|
+
case "documentation_standard_rule":
|
|
1354
|
+
return inferDocumentationStandardRuleArgs(text);
|
|
1355
|
+
case "warmup_policy_rule":
|
|
1356
|
+
return inferWarmupPolicyRuleArgs(text);
|
|
1357
|
+
case "visual_layout_rule":
|
|
1358
|
+
return inferVisualLayoutRuleArgs(text);
|
|
1359
|
+
case "enforcement_location_rule":
|
|
1360
|
+
return inferEnforcementLocationRuleArgs(text);
|
|
1361
|
+
case "reconciliation_rule":
|
|
1362
|
+
return inferReconciliationRuleArgs(text);
|
|
1363
|
+
case "throttle_policy_rule":
|
|
1364
|
+
return inferThrottlePolicyRuleArgs(text);
|
|
257
1365
|
case "has_unsaved_changes":
|
|
258
1366
|
return [subject, lower.includes("no unsaved") ? "false" : "true"];
|
|
259
1367
|
case "commit_action":
|
|
@@ -285,10 +1393,497 @@ function inferArgs(schema, text, subject) {
|
|
|
285
1393
|
return [subject, inferEvent(text)];
|
|
286
1394
|
case "acceptance_rule":
|
|
287
1395
|
return [subject, slug(text).slice(0, 64) || "observable_outcome"];
|
|
1396
|
+
case "permission_rule":
|
|
1397
|
+
return inferPermissionRuleArgs(text);
|
|
1398
|
+
case "default_value":
|
|
1399
|
+
return inferDefaultValueArgs(text, subject);
|
|
1400
|
+
case "uniqueness_constraint":
|
|
1401
|
+
return inferUniquenessArgs(text);
|
|
1402
|
+
case "state_membership":
|
|
1403
|
+
return inferStateMembershipArgs(text, subject);
|
|
1404
|
+
case "temporal_order":
|
|
1405
|
+
return inferTemporalOrderArgs(text, subject);
|
|
1406
|
+
case "conditional_behavior":
|
|
1407
|
+
return inferConditionalBehaviorArgs(text, subject);
|
|
1408
|
+
case "state_transition":
|
|
1409
|
+
return inferStateTransitionArgs(text, subject);
|
|
1410
|
+
case "rate_limit":
|
|
1411
|
+
return inferRateLimitArgs(text);
|
|
288
1412
|
default:
|
|
289
1413
|
return schema.argument_names.map((name) => name === "subject" ? subject : "unknown");
|
|
290
1414
|
}
|
|
291
1415
|
}
|
|
1416
|
+
function normalizePredicateToken(value) {
|
|
1417
|
+
return value
|
|
1418
|
+
.trim()
|
|
1419
|
+
.replace(/\b(?:a|an|the)\b\s*/gi, "")
|
|
1420
|
+
.replace(/['’]/g, "")
|
|
1421
|
+
.replace(/\s+/g, "_")
|
|
1422
|
+
.replace(/[^A-Za-z0-9_-]+/g, "_")
|
|
1423
|
+
.replace(/^_+|_+$/g, "");
|
|
1424
|
+
}
|
|
1425
|
+
function singularize(value) {
|
|
1426
|
+
if (["changes", "status", "results"].includes(value))
|
|
1427
|
+
return value;
|
|
1428
|
+
return value.endsWith("s") && value.length > 3 ? value.slice(0, -1) : value;
|
|
1429
|
+
}
|
|
1430
|
+
function normalizeSubjectKey(value) {
|
|
1431
|
+
return slug(value).split("_").map(singularize).join(".");
|
|
1432
|
+
}
|
|
1433
|
+
function escapeRegExp(value) {
|
|
1434
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
1435
|
+
}
|
|
1436
|
+
function matchesKeyword(text, lowerText, keyword) {
|
|
1437
|
+
const normalized = keyword.toLowerCase();
|
|
1438
|
+
if (/^[a-z0-9\s-]+$/.test(normalized)) {
|
|
1439
|
+
const pattern = escapeRegExp(normalized).replace(/\s+/g, "\\s+");
|
|
1440
|
+
return new RegExp(`\\b${pattern}\\b`, "i").test(text);
|
|
1441
|
+
}
|
|
1442
|
+
return lowerText.includes(normalized);
|
|
1443
|
+
}
|
|
1444
|
+
function inferPermissionRuleArgs(text) {
|
|
1445
|
+
const prohibition = text.match(/^(?<actor>[a-z][a-z\s_-]*?)\s+(?:must\s+not|cannot|can't|is\s+forbidden\s+to)\s+(?<action>[a-z][a-z_-]*)\s+(?<resource>.+?)\.?$/i);
|
|
1446
|
+
if (prohibition?.groups) {
|
|
1447
|
+
return [
|
|
1448
|
+
singularize(slug(prohibition.groups.actor ?? "actor")),
|
|
1449
|
+
normalizePredicateToken(prohibition.groups.action ?? "action"),
|
|
1450
|
+
normalizePredicateToken(prohibition.groups.resource ?? "resource"),
|
|
1451
|
+
"deny",
|
|
1452
|
+
];
|
|
1453
|
+
}
|
|
1454
|
+
const permission = text.match(/^(?:only\s+)?(?<actor>[a-z][a-z\s_-]*?)\s+(?:may|can|is\s+allowed\s+to)\s+(?<action>[a-z][a-z_-]*)\s+(?<resource>.+?)(?:\s+when\s+.+)?\.?$/i);
|
|
1455
|
+
return [
|
|
1456
|
+
singularize(slug(permission?.groups?.actor ?? "actor")),
|
|
1457
|
+
normalizePredicateToken(permission?.groups?.action ?? "action"),
|
|
1458
|
+
normalizePredicateToken(permission?.groups?.resource ?? "resource"),
|
|
1459
|
+
"assert",
|
|
1460
|
+
];
|
|
1461
|
+
}
|
|
1462
|
+
function inferGuardArgs(text, subject) {
|
|
1463
|
+
const disabledUntil = text.match(/^(?<subject>.+?)\s+(?:must|shall|should)?\s*(?:stay|remain)?\s*disabled\s+until\s+(?<condition>.+?)\.?$/i);
|
|
1464
|
+
if (disabledUntil?.groups?.subject && disabledUntil.groups.condition) {
|
|
1465
|
+
return [
|
|
1466
|
+
slug(disabledUntil.groups.subject),
|
|
1467
|
+
normalizePredicateToken(disabledUntil.groups.condition),
|
|
1468
|
+
"disabled",
|
|
1469
|
+
];
|
|
1470
|
+
}
|
|
1471
|
+
const lower = text.toLowerCase();
|
|
1472
|
+
return [
|
|
1473
|
+
subject,
|
|
1474
|
+
lower.includes("readonly") ? "isReadOnly" : "condition",
|
|
1475
|
+
"true",
|
|
1476
|
+
];
|
|
1477
|
+
}
|
|
1478
|
+
function inferExceptionRuleArgs(text, subject) {
|
|
1479
|
+
const exception = text.match(/^(?:the\s+)?(?<subject>[a-z][a-z\s_-]*?)\s+(?:must|shall|should)\s+(?<behavior>.+?)\s+unless\s+(?:the\s+)?(?<exception>.+?)\.?$/i);
|
|
1480
|
+
return [
|
|
1481
|
+
exception?.groups?.subject
|
|
1482
|
+
? normalizeSubjectKey(exception.groups.subject)
|
|
1483
|
+
: subject,
|
|
1484
|
+
normalizePredicateToken(exception?.groups?.behavior ?? "behavior"),
|
|
1485
|
+
normalizePredicateToken(exception?.groups?.exception ?? "exception"),
|
|
1486
|
+
];
|
|
1487
|
+
}
|
|
1488
|
+
function inferMutualExclusionArgs(text) {
|
|
1489
|
+
const exclusion = text.match(/^(?<left>.+?)\s+and\s+(?<right>.+?)\s+(?:must|shall|should)\s+be\s+mutually\s+exclusive\.?$/i);
|
|
1490
|
+
return [
|
|
1491
|
+
slug(exclusion?.groups?.left ?? "left"),
|
|
1492
|
+
slug(exclusion?.groups?.right ?? "right"),
|
|
1493
|
+
];
|
|
1494
|
+
}
|
|
1495
|
+
function inferDependencyRuleArgs(text) {
|
|
1496
|
+
const dependency = text.match(/^(?<subject>.+?)\s+requires\s+(?<prerequisite>.+?)\s+before\s+(?<dependent>.+?)\.?$/i);
|
|
1497
|
+
return [
|
|
1498
|
+
slug(dependency?.groups?.subject ?? "subject"),
|
|
1499
|
+
normalizePredicateToken(dependency?.groups?.prerequisite ?? "prerequisite"),
|
|
1500
|
+
normalizePredicateToken(dependency?.groups?.dependent ?? "dependent"),
|
|
1501
|
+
];
|
|
1502
|
+
}
|
|
1503
|
+
function inferOwnershipRuleArgs(text) {
|
|
1504
|
+
const ownership = text.match(/^(?<resource>.+?)\s+(?:is|are)\s+owned\s+by\s+(?:the\s+)?(?<owner>.+?)\.?$/i);
|
|
1505
|
+
return [
|
|
1506
|
+
slug(ownership?.groups?.resource ?? "resource"),
|
|
1507
|
+
slug(ownership?.groups?.owner ?? "owner"),
|
|
1508
|
+
];
|
|
1509
|
+
}
|
|
1510
|
+
function inferRetryPolicyArgs(text) {
|
|
1511
|
+
const retry = text.match(/^(?<subject>.+?)\s+(?:must|shall|should)\s+retry\s+up\s+to\s+(?<count>\d+)\s+(?<unit>times|attempts?)\.?$/i);
|
|
1512
|
+
return [
|
|
1513
|
+
slug(retry?.groups?.subject ?? "subject"),
|
|
1514
|
+
retry?.groups?.count ?? "0",
|
|
1515
|
+
slug(retry?.groups?.unit ?? "times"),
|
|
1516
|
+
];
|
|
1517
|
+
}
|
|
1518
|
+
function inferEscalationRuleArgs(text) {
|
|
1519
|
+
const escalation = text.match(/^(?<subject>.+?)\s+(?:must|shall|should)\s+escalate\s+to\s+(?<target>.+?)\s+after\s+(?<delay>\d+)\s+(?<unit>[a-z]+)\.?$/i);
|
|
1520
|
+
return [
|
|
1521
|
+
slug(escalation?.groups?.subject ?? "subject"),
|
|
1522
|
+
slug(escalation?.groups?.target ?? "target"),
|
|
1523
|
+
escalation?.groups?.delay ?? "0",
|
|
1524
|
+
slug(escalation?.groups?.unit ?? "unit"),
|
|
1525
|
+
];
|
|
1526
|
+
}
|
|
1527
|
+
function inferAvailabilitySlaArgs(text) {
|
|
1528
|
+
const availability = text.match(/^(?<subject>.+?)\s+availability\s+(?:must|shall|should)\s+be\s+at\s+least\s+(?<threshold>\d+(?:\.\d+)?)\s+(?<unit>percent|%)\s+(?<window>[a-z]+)\.?$/i);
|
|
1529
|
+
return [
|
|
1530
|
+
slug(availability?.groups?.subject ?? "subject"),
|
|
1531
|
+
availability?.groups?.threshold ?? "0",
|
|
1532
|
+
availability?.groups?.unit === "%"
|
|
1533
|
+
? "percent"
|
|
1534
|
+
: slug(availability?.groups?.unit ?? "percent"),
|
|
1535
|
+
slug(availability?.groups?.window ?? "window"),
|
|
1536
|
+
];
|
|
1537
|
+
}
|
|
1538
|
+
function inferNotificationRouteArgs(text) {
|
|
1539
|
+
const notification = text.match(/^(?<subject>.+?)\s+(?:must|shall|should)\s+notify\s+(?<recipient>.+?)\s+by\s+(?<channel>[a-z]+)\.?$/i);
|
|
1540
|
+
return [
|
|
1541
|
+
slug(notification?.groups?.subject ?? "subject"),
|
|
1542
|
+
slug(notification?.groups?.recipient ?? "recipient"),
|
|
1543
|
+
slug(notification?.groups?.channel ?? "channel"),
|
|
1544
|
+
];
|
|
1545
|
+
}
|
|
1546
|
+
function inferIdempotencyRuleArgs(text) {
|
|
1547
|
+
const idempotency = text.match(/^(?<subject>.+?)\s+(?:must|shall|should)\s+be\s+idempotent\s+by\s+(?<key>.+?)\.?$/i);
|
|
1548
|
+
const deduplicated = text.match(/^(?<subject>.+?)\s+(?:must|shall|should)\s+be\s+deduplicated\s+to\s+prevent\s+redundant\s+requests\s+during\s+(?<key>.+?)\.?$/i);
|
|
1549
|
+
return [
|
|
1550
|
+
slug(idempotency?.groups?.subject ??
|
|
1551
|
+
deduplicated?.groups?.subject ??
|
|
1552
|
+
"subject"),
|
|
1553
|
+
slug(idempotency?.groups?.key ??
|
|
1554
|
+
deduplicated?.groups?.key ??
|
|
1555
|
+
"idempotency_key"),
|
|
1556
|
+
];
|
|
1557
|
+
}
|
|
1558
|
+
function inferDataResidencyRuleArgs(text) {
|
|
1559
|
+
const residency = text.match(/^(?<subject>.+?)\s+(?:must|shall|should)\s+be\s+(?:stored|processed|kept)\s+in\s+(?:the\s+)?(?<region>.+?\b(?:region|jurisdiction|country|zone|area))\.?$/i);
|
|
1560
|
+
return [
|
|
1561
|
+
slug(residency?.groups?.subject ?? "data"),
|
|
1562
|
+
slug(residency?.groups?.region ?? "region"),
|
|
1563
|
+
];
|
|
1564
|
+
}
|
|
1565
|
+
function inferAuditEventRuleArgs(text) {
|
|
1566
|
+
const audit = text.match(/^(?<subject>.+?)\s+(?:must|shall|should)\s+be\s+(?:recorded|logged|audited)\s+in\s+(?:the\s+)?(?<log>audit\s+(?:log|trail))\.?$/i);
|
|
1567
|
+
return [
|
|
1568
|
+
slug(audit?.groups?.subject ?? "subject"),
|
|
1569
|
+
slug(audit?.groups?.log ?? "audit_log"),
|
|
1570
|
+
];
|
|
1571
|
+
}
|
|
1572
|
+
function inferConsentRuleArgs(text) {
|
|
1573
|
+
const consent = text.match(/^(?<subject>.+?)\s+(?:must|shall|should)\s+require\s+(?<consent>.+?consent)\s+before\s+(?<purpose>.+?)\.?$/i);
|
|
1574
|
+
return [
|
|
1575
|
+
slug(consent?.groups?.subject ?? "subject"),
|
|
1576
|
+
slug(consent?.groups?.consent ?? "consent"),
|
|
1577
|
+
normalizePredicateToken(consent?.groups?.purpose ?? "purpose"),
|
|
1578
|
+
];
|
|
1579
|
+
}
|
|
1580
|
+
function inferLifecycleRuleArgs(text) {
|
|
1581
|
+
const lifecycle = text.match(/^(?<subject>.+?)\s+(?:must|shall|should)\s+be\s+(?<action>archived|deleted|expired)\s+after\s+(?<duration>\d+)\s+(?<unit>[a-z]+)\.?$/i);
|
|
1582
|
+
return [
|
|
1583
|
+
slug(lifecycle?.groups?.subject ?? "subject"),
|
|
1584
|
+
slug(lifecycle?.groups?.action ?? "action"),
|
|
1585
|
+
lifecycle?.groups?.duration ?? "0",
|
|
1586
|
+
slug(lifecycle?.groups?.unit ?? "unit"),
|
|
1587
|
+
];
|
|
1588
|
+
}
|
|
1589
|
+
function inferConflictResolutionRuleArgs(text) {
|
|
1590
|
+
const conflict = text.match(/^when\s+(?<subject>.+?)\s+conflicts?,\s+(?:the\s+)?(?<strategy>.+?)\.?$/i);
|
|
1591
|
+
return [
|
|
1592
|
+
slug(conflict?.groups?.subject ?? "subject"),
|
|
1593
|
+
normalizePredicateToken(conflict?.groups?.strategy ?? "strategy"),
|
|
1594
|
+
];
|
|
1595
|
+
}
|
|
1596
|
+
function inferFallbackRuleArgs(text) {
|
|
1597
|
+
const fallback = text.match(/^if\s+(?<condition>.+?),\s+(?<subject>.+?)\s+(?:must|shall|should)\s+fall\s+back\s+to\s+(?<target>.+?)\.?$/i);
|
|
1598
|
+
return [
|
|
1599
|
+
normalizePredicateToken(fallback?.groups?.condition ?? "condition"),
|
|
1600
|
+
slug(fallback?.groups?.subject ?? "subject"),
|
|
1601
|
+
normalizePredicateToken(fallback?.groups?.target ?? "fallback"),
|
|
1602
|
+
];
|
|
1603
|
+
}
|
|
1604
|
+
function inferBatchOperationRuleArgs(text) {
|
|
1605
|
+
const batch = text.match(/^(?<subject>.+?)\s+(?:must|shall|should)\s+process\s+(?<resource>.+?)\s+in\s+batches\s+of\s+(?<size>\d+)\.?$/i);
|
|
1606
|
+
return [
|
|
1607
|
+
slug(batch?.groups?.subject ?? "subject"),
|
|
1608
|
+
slug(batch?.groups?.resource ?? "resource"),
|
|
1609
|
+
batch?.groups?.size ?? "0",
|
|
1610
|
+
];
|
|
1611
|
+
}
|
|
1612
|
+
function inferConsistencyRuleArgs(text) {
|
|
1613
|
+
const consistency = text.match(/^(?<subject>.+?)\s+(?:must|shall|should)\s+reference\s+(?<target>an?\s+existing\s+.+?)\.?$/i);
|
|
1614
|
+
return [
|
|
1615
|
+
slug(consistency?.groups?.subject ?? "subject"),
|
|
1616
|
+
normalizePredicateToken(consistency?.groups?.target ?? "target"),
|
|
1617
|
+
];
|
|
1618
|
+
}
|
|
1619
|
+
function inferBuildConstraintArgs(text) {
|
|
1620
|
+
const build = text.match(/^(?<subject>.+?)\s+(?:must|shall|should)\s+be\s+(?<property>deterministic)\s+at\s+(?<scope>build\s+time)\.?$/i);
|
|
1621
|
+
return [
|
|
1622
|
+
slug(build?.groups?.subject ?? "subject"),
|
|
1623
|
+
normalizePredicateToken(build?.groups?.property ?? "property"),
|
|
1624
|
+
normalizePredicateToken(build?.groups?.scope ?? "build_time"),
|
|
1625
|
+
];
|
|
1626
|
+
}
|
|
1627
|
+
function inferEnvironmentSafetyRuleArgs(text) {
|
|
1628
|
+
const safety = text.match(/^(?<action>.+?)\s+(?:must|shall|should)\s+be\s+(?<decision>forbidden|read-only|allowed)\s+in\s+(?<environment>production|staging|development)\.?$/i);
|
|
1629
|
+
return [
|
|
1630
|
+
slug(safety?.groups?.action ?? "action"),
|
|
1631
|
+
normalizePredicateToken(safety?.groups?.decision ?? "decision"),
|
|
1632
|
+
slug(safety?.groups?.environment ?? "environment"),
|
|
1633
|
+
];
|
|
1634
|
+
}
|
|
1635
|
+
function inferSchemaInvariantRuleArgs(text) {
|
|
1636
|
+
const invariant = text.match(/^(?<field>.+?)\s+(?:must|shall|should)\s+be\s+(?<kind>immutable)\s+after\s+(?<scope>.+?)\.?$/i);
|
|
1637
|
+
return [
|
|
1638
|
+
slug(invariant?.groups?.field ?? "field"),
|
|
1639
|
+
normalizePredicateToken(invariant?.groups?.kind ?? "invariant"),
|
|
1640
|
+
normalizePredicateToken(invariant?.groups?.scope ? `after ${invariant.groups.scope}` : "scope"),
|
|
1641
|
+
];
|
|
1642
|
+
}
|
|
1643
|
+
function inferCodingStandardRuleArgs(text) {
|
|
1644
|
+
const standard = text.match(/^(?<subject>.+?)\s+(?:must|shall|should)\s+(?<action>use|avoid)\s+(?<target>.+?)\.?$/i);
|
|
1645
|
+
return [
|
|
1646
|
+
slug(standard?.groups?.subject ?? "subject"),
|
|
1647
|
+
normalizePredicateToken(standard?.groups?.action ?? "action"),
|
|
1648
|
+
normalizePredicateToken(standard?.groups?.target ?? "target"),
|
|
1649
|
+
];
|
|
1650
|
+
}
|
|
1651
|
+
function inferMigrationBoundaryRuleArgs(text) {
|
|
1652
|
+
const migration = text.match(/^(?<subject>.+?)\s+may\s+only\s+be\s+(?<action>read)\s+as\s+(?<scope>migration\s+input)(?:\s+by\s+.+?)?\.?$/i);
|
|
1653
|
+
return [
|
|
1654
|
+
slug(migration?.groups?.subject ?? "legacy_input"),
|
|
1655
|
+
normalizePredicateToken(migration?.groups?.action ?? "action"),
|
|
1656
|
+
normalizePredicateToken(migration?.groups?.scope ?? "migration_input"),
|
|
1657
|
+
];
|
|
1658
|
+
}
|
|
1659
|
+
function inferAbsenceRequirementArgs(text) {
|
|
1660
|
+
const absence = text.match(/^(?<subject>.+?)\s+(?:must|shall|should)\s+be\s+(?<state>absent|removed)\.?$/i);
|
|
1661
|
+
const declarativeAbsence = text.match(/^no\s+(?<subject>.+?)\.?$/i);
|
|
1662
|
+
return [
|
|
1663
|
+
normalizePredicateToken(absence?.groups?.subject ??
|
|
1664
|
+
declarativeAbsence?.groups?.subject ??
|
|
1665
|
+
"subject"),
|
|
1666
|
+
normalizePredicateToken(absence?.groups?.state ?? "absent"),
|
|
1667
|
+
];
|
|
1668
|
+
}
|
|
1669
|
+
function inferOfflineBehaviorRuleArgs(text) {
|
|
1670
|
+
const offline = text.match(/^(?<subject>.+?)\s+(?:must|shall|should)\s+be\s+(?<behavior>non-blocking|resilient)\s+during\s+(?<condition>offline\s+conditions)\.?$/i);
|
|
1671
|
+
return [
|
|
1672
|
+
slug(offline?.groups?.subject ?? "subject"),
|
|
1673
|
+
normalizePredicateToken(offline?.groups?.behavior ?? "behavior"),
|
|
1674
|
+
normalizePredicateToken(offline?.groups?.condition ?? "offline_conditions"),
|
|
1675
|
+
];
|
|
1676
|
+
}
|
|
1677
|
+
function inferReleaseGateRuleArgs(text) {
|
|
1678
|
+
const release = text.match(/^(?<subject>.+?)\s+(?:must|shall|should)\s+pass\s+(?<gate>.+?)\s+before\s+(?<target>.+?)\.?$/i);
|
|
1679
|
+
return [
|
|
1680
|
+
slug(release?.groups?.subject ?? "builds"),
|
|
1681
|
+
normalizePredicateToken(release?.groups?.gate ?? "gate"),
|
|
1682
|
+
slug(release?.groups?.target ?? "target"),
|
|
1683
|
+
];
|
|
1684
|
+
}
|
|
1685
|
+
function inferPlatformConsistencyRuleArgs(text) {
|
|
1686
|
+
const platform = text.match(/^(?<subject>.+?)\s+(?:must|shall|should)\s+synchronize\s+across\s+(?<platforms>.+?)\.?$/i);
|
|
1687
|
+
const platforms = (platform?.groups?.platforms ?? "platform")
|
|
1688
|
+
.split(/,|\band\b/i)
|
|
1689
|
+
.map((part) => slug(part.trim()))
|
|
1690
|
+
.filter((part) => part.length > 0)
|
|
1691
|
+
.join(",");
|
|
1692
|
+
return [slug(platform?.groups?.subject ?? "subject"), platforms];
|
|
1693
|
+
}
|
|
1694
|
+
function inferPreservationRuleArgs(text) {
|
|
1695
|
+
const preservation = text.match(/^(?<subject>.+?)\s+(?:must|shall|should)\s+preserve\s+(?<preserved>.+?)\s+when\s+(?:the\s+)?(?<condition>.+?)\.?$/i);
|
|
1696
|
+
return [
|
|
1697
|
+
slug(preservation?.groups?.subject ?? "subject"),
|
|
1698
|
+
slug(preservation?.groups?.preserved ?? "preserved"),
|
|
1699
|
+
normalizePredicateToken(preservation?.groups?.condition ?? "condition"),
|
|
1700
|
+
];
|
|
1701
|
+
}
|
|
1702
|
+
function inferAbstractionBoundaryRuleArgs(text) {
|
|
1703
|
+
const boundary = text.match(/^(?<subject>.+?)\s+(?:must|shall|should)\s+be\s+persisted\s+as\s+(?<contract>.+?)\.?$/i);
|
|
1704
|
+
return [
|
|
1705
|
+
slug(boundary?.groups?.subject ?? "subject"),
|
|
1706
|
+
"persisted_as",
|
|
1707
|
+
normalizePredicateToken(boundary?.groups?.contract ?? "contract"),
|
|
1708
|
+
];
|
|
1709
|
+
}
|
|
1710
|
+
function inferSecurityConfigurationRuleArgs(text) {
|
|
1711
|
+
const config = text.match(/^(?<subject>.+?)\s+(?:must|shall|should)\s+have\s+explicit\s+(?<setting>[A-Za-z0-9_.-]+)\s+(?<value>[A-Za-z0-9_.-]+)\.?$/i);
|
|
1712
|
+
return [
|
|
1713
|
+
slug(config?.groups?.subject ?? "subject"),
|
|
1714
|
+
slug(config?.groups?.setting ?? "setting"),
|
|
1715
|
+
slug(config?.groups?.value ?? "value"),
|
|
1716
|
+
];
|
|
1717
|
+
}
|
|
1718
|
+
function inferOrderedStrategyRuleArgs(text) {
|
|
1719
|
+
const ordered = text.match(/^(?<subject>.+?)\s+(?:must|shall|should)\s+use\s+(?<kind>.+?)\s+in\s+priority\s+order\s+(?<values>.+?)\.?$/i);
|
|
1720
|
+
const values = (ordered?.groups?.values ?? "")
|
|
1721
|
+
.split(/,|>/)
|
|
1722
|
+
.map((value) => normalizePredicateToken(value))
|
|
1723
|
+
.filter((value) => value.length > 0)
|
|
1724
|
+
.join(",");
|
|
1725
|
+
return [
|
|
1726
|
+
slug(ordered?.groups?.subject ?? "subject"),
|
|
1727
|
+
slug(ordered?.groups?.kind ?? "strategy"),
|
|
1728
|
+
values || "ordered_values",
|
|
1729
|
+
];
|
|
1730
|
+
}
|
|
1731
|
+
function inferRefreshPolicyRuleArgs(text) {
|
|
1732
|
+
const refresh = text.match(/^(?<subject>.+?)\s+(?:must|shall|should)\s+automatically\s+refresh\s+(?<target>.+?)\s+without\s+requiring\s+manual\s+page\s+reload\.?$/i);
|
|
1733
|
+
return [
|
|
1734
|
+
slug(refresh?.groups?.subject ?? "subject"),
|
|
1735
|
+
slug(refresh?.groups?.target ?? "target"),
|
|
1736
|
+
"automatic",
|
|
1737
|
+
];
|
|
1738
|
+
}
|
|
1739
|
+
function inferScopedAuthorizationRuleArgs(text) {
|
|
1740
|
+
const scoped = text.match(/^(?<actor>.+?)\s+(?:must|shall|should)\s+be\s+denied\s+(?<action>.+?)\.?$/i);
|
|
1741
|
+
return [
|
|
1742
|
+
slug(scoped?.groups?.actor ?? "actor"),
|
|
1743
|
+
slug(scoped?.groups?.action ?? "action"),
|
|
1744
|
+
"deny",
|
|
1745
|
+
];
|
|
1746
|
+
}
|
|
1747
|
+
function inferDocumentationStandardRuleArgs(text) {
|
|
1748
|
+
const docs = text.match(/^(?<subject>.+?)\s+(?:must|shall|should)\s+be\s+documented\s+in\s+(?<artifact>.+?)\.?$/i);
|
|
1749
|
+
return [
|
|
1750
|
+
slug(docs?.groups?.subject ?? "subject"),
|
|
1751
|
+
"documented_in",
|
|
1752
|
+
slug(docs?.groups?.artifact ?? "documentation"),
|
|
1753
|
+
];
|
|
1754
|
+
}
|
|
1755
|
+
function inferWarmupPolicyRuleArgs(text) {
|
|
1756
|
+
const warmup = text.match(/^(?:the\s+)?(?<subject>.+?)\s+(?:must|shall|should)\s+warm\s+up\s+on\s+(?<trigger>.+?)\.?$/i);
|
|
1757
|
+
return [
|
|
1758
|
+
slug(warmup?.groups?.subject ?? "subject"),
|
|
1759
|
+
slug(warmup?.groups?.trigger ?? "trigger"),
|
|
1760
|
+
];
|
|
1761
|
+
}
|
|
1762
|
+
function inferVisualLayoutRuleArgs(text) {
|
|
1763
|
+
const layout = text.match(/^(?<subject>.+?)\s+(?:must|shall|should)\s+remain\s+visually\s+aligned\s+with\s+(?<target>.+?)\.?$/i);
|
|
1764
|
+
return [
|
|
1765
|
+
slug(layout?.groups?.subject ?? "subject"),
|
|
1766
|
+
"aligned_with",
|
|
1767
|
+
slug(layout?.groups?.target ?? "target"),
|
|
1768
|
+
];
|
|
1769
|
+
}
|
|
1770
|
+
function inferEnforcementLocationRuleArgs(text) {
|
|
1771
|
+
const enforcement = text.match(/^(?<subject>.+?)\s+(?:must|shall|should)\s+be\s+enforced\s+at\s+(?<location>.+?)\.?$/i);
|
|
1772
|
+
return [
|
|
1773
|
+
slug(enforcement?.groups?.subject ?? "subject"),
|
|
1774
|
+
slug(enforcement?.groups?.location ?? "location"),
|
|
1775
|
+
];
|
|
1776
|
+
}
|
|
1777
|
+
function inferReconciliationRuleArgs(text) {
|
|
1778
|
+
const reconciliation = text.match(/^on\s+(?<trigger>.+?),\s*(?<subject>.+?)\s+(?:must|shall|should)\s+reconcile\s+(?<target>.+?)\s+and\s+(?<action>clear\s+stale\s+.+?)\.?$/i);
|
|
1779
|
+
return [
|
|
1780
|
+
slug(reconciliation?.groups?.subject ?? "subject"),
|
|
1781
|
+
slug(reconciliation?.groups?.trigger ?? "trigger"),
|
|
1782
|
+
slug(reconciliation?.groups?.target ?? "target"),
|
|
1783
|
+
slug(reconciliation?.groups?.action ?? "action"),
|
|
1784
|
+
];
|
|
1785
|
+
}
|
|
1786
|
+
function inferThrottlePolicyRuleArgs(text) {
|
|
1787
|
+
const throttle = text.match(/^(?<subject>.+?)\s+(?:must|shall|should)\s+be\s+throttled\s+for\s+(?<condition>.+?)\.?$/i);
|
|
1788
|
+
return [
|
|
1789
|
+
slug(throttle?.groups?.subject ?? "subject"),
|
|
1790
|
+
normalizePredicateToken(throttle?.groups?.condition ?? "condition"),
|
|
1791
|
+
];
|
|
1792
|
+
}
|
|
1793
|
+
function inferDefaultValueArgs(text, subject) {
|
|
1794
|
+
const defaultValue = text.match(/^(?:the\s+)?(?<subject>[a-z][a-z\s_-]*?)\s+defaults?\s+to\s+(?<value>[a-z][a-z0-9\s_-]*?)(?:\s+(?<property>mode|state|status))?\.?$/i);
|
|
1795
|
+
return [
|
|
1796
|
+
defaultValue?.groups?.subject
|
|
1797
|
+
? normalizeSubjectKey(defaultValue.groups.subject)
|
|
1798
|
+
: subject,
|
|
1799
|
+
slug(defaultValue?.groups?.property ?? "value"),
|
|
1800
|
+
slug(defaultValue?.groups?.value ?? "value"),
|
|
1801
|
+
];
|
|
1802
|
+
}
|
|
1803
|
+
function inferUniquenessArgs(text) {
|
|
1804
|
+
const uniqueness = text.match(/^(?:there\s+)?(?:must|shall|should)\s+be\s+at\s+most\s+one\s+(?<subject>[a-z][a-z\s_-]*?)\s+per\s+(?<scope>.+?)\.?$/i);
|
|
1805
|
+
const scope = (uniqueness?.groups?.scope ?? "scope")
|
|
1806
|
+
.split(/\s+per\s+/i)
|
|
1807
|
+
.map((part) => part.trim())
|
|
1808
|
+
.filter((part) => part.length > 0)
|
|
1809
|
+
.map(normalizePredicateToken)
|
|
1810
|
+
.join(",");
|
|
1811
|
+
return [slug(uniqueness?.groups?.subject ?? "subject"), scope];
|
|
1812
|
+
}
|
|
1813
|
+
function inferStateMembershipArgs(text, subject) {
|
|
1814
|
+
const stateMembership = text.match(/^(?<subject>.+?)\s+(?:terminal\s+)?states\s+are\s+(?<states>.+?)\.?$/i);
|
|
1815
|
+
const states = (stateMembership?.groups?.states ?? "state")
|
|
1816
|
+
.split(/,|\band\b|\bor\b/i)
|
|
1817
|
+
.map((state) => state.trim())
|
|
1818
|
+
.filter((state) => state.length > 0)
|
|
1819
|
+
.map(normalizePredicateToken)
|
|
1820
|
+
.join(",");
|
|
1821
|
+
return [
|
|
1822
|
+
stateMembership?.groups?.subject
|
|
1823
|
+
? normalizeSubjectKey(stateMembership.groups.subject)
|
|
1824
|
+
: subject,
|
|
1825
|
+
states,
|
|
1826
|
+
];
|
|
1827
|
+
}
|
|
1828
|
+
function inferTemporalOrderArgs(text, subject) {
|
|
1829
|
+
const initializesAfter = text.match(/^(?:the\s+)?(?<subject>.+?)\s+initializes\s+after\s+(?:the\s+)?(?<ready>.+?)\s+is\s+ready\.?$/i);
|
|
1830
|
+
if (initializesAfter?.groups?.subject && initializesAfter.groups.ready) {
|
|
1831
|
+
return [
|
|
1832
|
+
slug(initializesAfter.groups.subject),
|
|
1833
|
+
`${slug(initializesAfter.groups.ready)}_ready`,
|
|
1834
|
+
"initializes",
|
|
1835
|
+
];
|
|
1836
|
+
}
|
|
1837
|
+
const temporal = text.match(/^(?<subject>.+?)\s+(?:must|shall|should)\s+be\s+(?<before>[a-z][a-z\s_-]*?)\s+before\s+(?<after>.+?)\.?$/i);
|
|
1838
|
+
return [
|
|
1839
|
+
temporal?.groups?.subject
|
|
1840
|
+
? slug(temporal.groups.subject).replace(/_/g, ".")
|
|
1841
|
+
: subject,
|
|
1842
|
+
normalizePredicateToken(temporal?.groups?.before ?? "before_event"),
|
|
1843
|
+
normalizePredicateToken(temporal?.groups?.after ?? "after_event"),
|
|
1844
|
+
];
|
|
1845
|
+
}
|
|
1846
|
+
function inferConditionalBehaviorArgs(text, subject) {
|
|
1847
|
+
const whenMust = text.match(/^when\s+(?<condition>.+?),\s*(?:the\s+)?(?<subject>.+?)\s+(?:must|shall|should)\s+(?<behavior>.+?)\.?$/i);
|
|
1848
|
+
if (whenMust?.groups?.condition && whenMust.groups.subject) {
|
|
1849
|
+
return [
|
|
1850
|
+
slug(whenMust.groups.subject),
|
|
1851
|
+
normalizePredicateToken(whenMust.groups.condition),
|
|
1852
|
+
normalizePredicateToken(whenMust.groups.behavior ?? "behavior"),
|
|
1853
|
+
];
|
|
1854
|
+
}
|
|
1855
|
+
const conditional = text.match(/^if\s+(?:(?:a|an|the)\s+)?(?<conditionSubject>[a-z][a-z_-]*)\s+(?<condition>.+?),\s*(?:it|they|the\s+[a-z][a-z\s_-]*?)\s+(?<behavior>.+?)\.?$/i);
|
|
1856
|
+
return [
|
|
1857
|
+
conditional?.groups?.conditionSubject
|
|
1858
|
+
? singularize(slug(conditional.groups.conditionSubject))
|
|
1859
|
+
: subject,
|
|
1860
|
+
normalizePredicateToken(conditional?.groups?.condition ?? "condition"),
|
|
1861
|
+
normalizePredicateToken(conditional?.groups?.behavior ?? "behavior"),
|
|
1862
|
+
];
|
|
1863
|
+
}
|
|
1864
|
+
function inferStateTransitionArgs(text, subject) {
|
|
1865
|
+
const transition = text.match(/^when\s+(?<trigger>.+?),\s*(?:the\s+)?(?<subject>[a-z][a-z\s_-]*?)\s+transitions?\s+from\s+(?<from>[a-z][a-z0-9_-]*)\s+to\s+(?<to>[a-z][a-z0-9_-]*)\.?$/i);
|
|
1866
|
+
return [
|
|
1867
|
+
transition?.groups?.subject
|
|
1868
|
+
? normalizeSubjectKey(transition.groups.subject)
|
|
1869
|
+
: subject,
|
|
1870
|
+
normalizePredicateToken(transition?.groups?.from ?? "from_state"),
|
|
1871
|
+
normalizePredicateToken(transition?.groups?.to ?? "to_state"),
|
|
1872
|
+
normalizePredicateToken(transition?.groups?.trigger ?? inferTrigger(text)),
|
|
1873
|
+
];
|
|
1874
|
+
}
|
|
1875
|
+
function inferRateLimitArgs(text) {
|
|
1876
|
+
const rateLimit = text.match(/^(?<subject>.+?)\s+must\s+be\s+rate\s+limited\s+to\s+(?<count>\d+)\s+(?<action>[a-z][a-z\s_-]*?)\s+per\s+(?<window>[a-z]+)\.?$/i);
|
|
1877
|
+
const subject = rateLimit?.groups?.subject
|
|
1878
|
+
? slug(rateLimit.groups.subject).replace(/_requests?$/, ".request")
|
|
1879
|
+
: "requirement.subject";
|
|
1880
|
+
return [
|
|
1881
|
+
subject,
|
|
1882
|
+
normalizePredicateToken(rateLimit?.groups?.action ?? "action"),
|
|
1883
|
+
normalizePredicateToken(rateLimit?.groups?.window ?? "window"),
|
|
1884
|
+
rateLimit?.groups?.count ?? "0",
|
|
1885
|
+
];
|
|
1886
|
+
}
|
|
292
1887
|
function inferDuration(text) {
|
|
293
1888
|
return text.match(/\b\d+\b/)?.[0] ?? "1";
|
|
294
1889
|
}
|
|
@@ -351,7 +1946,292 @@ function inferEvent(text) {
|
|
|
351
1946
|
}
|
|
352
1947
|
function scoreSchema(schema, text) {
|
|
353
1948
|
const lower = text.toLowerCase();
|
|
354
|
-
|
|
1949
|
+
if (schema.predicate_name === "guard") {
|
|
1950
|
+
if (/^.+?\s+(?:must|shall|should)?\s*(?:stay|remain)?\s*disabled\s+until\s+.+?\.?$/i.test(text)) {
|
|
1951
|
+
return 0.98;
|
|
1952
|
+
}
|
|
1953
|
+
}
|
|
1954
|
+
if (schema.predicate_name === "conditional_behavior") {
|
|
1955
|
+
if (/^if\s+(?:(?:a|an|the)\s+)?[a-z][a-z_-]*\s+.+?,\s*(?:it|they|the\s+[a-z][a-z\s_-]*?)\s+.+?\.?$/i.test(text)) {
|
|
1956
|
+
return 0.98;
|
|
1957
|
+
}
|
|
1958
|
+
if (/^when\s+.+?,\s*(?:the\s+)?.+?\s+(?:must|shall|should)\s+.+?\.?$/i.test(text)) {
|
|
1959
|
+
return 0.36;
|
|
1960
|
+
}
|
|
1961
|
+
return 0;
|
|
1962
|
+
}
|
|
1963
|
+
if (schema.predicate_name === "exception_rule") {
|
|
1964
|
+
if (!/^(?:the\s+)?[a-z][a-z\s_-]*?\s+(?:must|shall|should)\s+.+?\s+unless\s+(?:the\s+)?.+?\.?$/i.test(text)) {
|
|
1965
|
+
return 0;
|
|
1966
|
+
}
|
|
1967
|
+
return 0.98;
|
|
1968
|
+
}
|
|
1969
|
+
if (schema.predicate_name === "mutual_exclusion") {
|
|
1970
|
+
if (!/^.+?\s+and\s+.+?\s+(?:must|shall|should)\s+be\s+mutually\s+exclusive\.?$/i.test(text)) {
|
|
1971
|
+
return 0;
|
|
1972
|
+
}
|
|
1973
|
+
return 0.98;
|
|
1974
|
+
}
|
|
1975
|
+
if (schema.predicate_name === "dependency_rule") {
|
|
1976
|
+
if (!/^.+?\s+requires\s+.+?\s+before\s+.+?\.?$/i.test(text)) {
|
|
1977
|
+
return 0;
|
|
1978
|
+
}
|
|
1979
|
+
return 0.98;
|
|
1980
|
+
}
|
|
1981
|
+
if (schema.predicate_name === "ownership_rule") {
|
|
1982
|
+
if (!/^.+?\s+(?:is|are)\s+owned\s+by\s+(?:the\s+)?.+?\.?$/i.test(text)) {
|
|
1983
|
+
return 0;
|
|
1984
|
+
}
|
|
1985
|
+
return 0.98;
|
|
1986
|
+
}
|
|
1987
|
+
if (schema.predicate_name === "retry_policy") {
|
|
1988
|
+
if (!/^.+?\s+(?:must|shall|should)\s+retry\s+up\s+to\s+\d+\s+(?:times|attempts?)\.?$/i.test(text)) {
|
|
1989
|
+
return 0;
|
|
1990
|
+
}
|
|
1991
|
+
return 0.98;
|
|
1992
|
+
}
|
|
1993
|
+
if (schema.predicate_name === "escalation_rule") {
|
|
1994
|
+
if (!/^.+?\s+(?:must|shall|should)\s+escalate\s+to\s+.+?\s+after\s+\d+\s+[a-z]+\.?$/i.test(text)) {
|
|
1995
|
+
return 0;
|
|
1996
|
+
}
|
|
1997
|
+
return 0.98;
|
|
1998
|
+
}
|
|
1999
|
+
if (schema.predicate_name === "availability_sla") {
|
|
2000
|
+
if (!/^.+?\s+availability\s+(?:must|shall|should)\s+be\s+at\s+least\s+\d+(?:\.\d+)?\s+(?:percent|%)\s+[a-z]+\.?$/i.test(text)) {
|
|
2001
|
+
return 0;
|
|
2002
|
+
}
|
|
2003
|
+
return 0.98;
|
|
2004
|
+
}
|
|
2005
|
+
if (schema.predicate_name === "notification_route") {
|
|
2006
|
+
if (!/^.+?\s+(?:must|shall|should)\s+notify\s+.+?\s+by\s+[a-z]+\.?$/i.test(text)) {
|
|
2007
|
+
return 0;
|
|
2008
|
+
}
|
|
2009
|
+
return 0.98;
|
|
2010
|
+
}
|
|
2011
|
+
if (schema.predicate_name === "idempotency_rule") {
|
|
2012
|
+
if (!/^.+?\s+(?:must|shall|should)\s+be\s+idempotent\s+by\s+.+?\.?$/i.test(text) &&
|
|
2013
|
+
!/^.+?\s+(?:must|shall|should)\s+be\s+deduplicated\s+to\s+prevent\s+redundant\s+requests\s+during\s+.+?\.?$/i.test(text)) {
|
|
2014
|
+
return 0;
|
|
2015
|
+
}
|
|
2016
|
+
return 0.98;
|
|
2017
|
+
}
|
|
2018
|
+
if (schema.predicate_name === "data_residency_rule") {
|
|
2019
|
+
if (!/^.+?\s+(?:must|shall|should)\s+be\s+(?:stored|processed|kept)\s+in\s+(?:the\s+)?.+?\b(?:region|jurisdiction|country|zone|area)\.?$/i.test(text)) {
|
|
2020
|
+
return 0;
|
|
2021
|
+
}
|
|
2022
|
+
return 0.98;
|
|
2023
|
+
}
|
|
2024
|
+
if (schema.predicate_name === "audit_event_rule") {
|
|
2025
|
+
if (!/^.+?\s+(?:must|shall|should)\s+be\s+(?:recorded|logged|audited)\s+in\s+(?:the\s+)?audit\s+(?:log|trail)\.?$/i.test(text)) {
|
|
2026
|
+
return 0;
|
|
2027
|
+
}
|
|
2028
|
+
return 0.98;
|
|
2029
|
+
}
|
|
2030
|
+
if (schema.predicate_name === "consent_rule") {
|
|
2031
|
+
if (!/^.+?\s+(?:must|shall|should)\s+require\s+.+?consent\s+before\s+.+?\.?$/i.test(text)) {
|
|
2032
|
+
return 0;
|
|
2033
|
+
}
|
|
2034
|
+
return 0.98;
|
|
2035
|
+
}
|
|
2036
|
+
if (schema.predicate_name === "lifecycle_rule") {
|
|
2037
|
+
if (!/^.+?\s+(?:must|shall|should)\s+be\s+(?:archived|deleted|expired)\s+after\s+\d+\s+[a-z]+\.?$/i.test(text)) {
|
|
2038
|
+
return 0;
|
|
2039
|
+
}
|
|
2040
|
+
return 0.98;
|
|
2041
|
+
}
|
|
2042
|
+
if (schema.predicate_name === "conflict_resolution_rule") {
|
|
2043
|
+
if (!/^when\s+.+?\s+conflicts?,\s+(?:the\s+)?.+?\.?$/i.test(text)) {
|
|
2044
|
+
return 0;
|
|
2045
|
+
}
|
|
2046
|
+
return 0.98;
|
|
2047
|
+
}
|
|
2048
|
+
if (schema.predicate_name === "fallback_rule") {
|
|
2049
|
+
if (!/^if\s+.+?,\s+.+?\s+(?:must|shall|should)\s+fall\s+back\s+to\s+.+?\.?$/i.test(text)) {
|
|
2050
|
+
return 0;
|
|
2051
|
+
}
|
|
2052
|
+
return 0.98;
|
|
2053
|
+
}
|
|
2054
|
+
if (schema.predicate_name === "batch_operation_rule") {
|
|
2055
|
+
if (!/^.+?\s+(?:must|shall|should)\s+process\s+.+?\s+in\s+batches\s+of\s+\d+\.?$/i.test(text)) {
|
|
2056
|
+
return 0;
|
|
2057
|
+
}
|
|
2058
|
+
return 0.98;
|
|
2059
|
+
}
|
|
2060
|
+
if (schema.predicate_name === "consistency_rule") {
|
|
2061
|
+
if (!/^.+?\s+(?:must|shall|should)\s+reference\s+an?\s+existing\s+.+?\.?$/i.test(text)) {
|
|
2062
|
+
return 0;
|
|
2063
|
+
}
|
|
2064
|
+
return 0.98;
|
|
2065
|
+
}
|
|
2066
|
+
if (schema.predicate_name === "build_constraint") {
|
|
2067
|
+
if (!/^.+?\s+(?:must|shall|should)\s+be\s+deterministic\s+at\s+build\s+time\.?$/i.test(text)) {
|
|
2068
|
+
return 0;
|
|
2069
|
+
}
|
|
2070
|
+
return 0.98;
|
|
2071
|
+
}
|
|
2072
|
+
if (schema.predicate_name === "environment_safety_rule") {
|
|
2073
|
+
if (!/^.+?\s+(?:must|shall|should)\s+be\s+(?:forbidden|read-only|allowed)\s+in\s+(?:production|staging|development)\.?$/i.test(text)) {
|
|
2074
|
+
return 0;
|
|
2075
|
+
}
|
|
2076
|
+
return 0.98;
|
|
2077
|
+
}
|
|
2078
|
+
if (schema.predicate_name === "schema_invariant_rule") {
|
|
2079
|
+
if (!/^.+?\s+(?:must|shall|should)\s+be\s+immutable\s+after\s+.+?\.?$/i.test(text)) {
|
|
2080
|
+
return 0;
|
|
2081
|
+
}
|
|
2082
|
+
return 0.98;
|
|
2083
|
+
}
|
|
2084
|
+
if (schema.predicate_name === "coding_standard_rule") {
|
|
2085
|
+
if (!/^.+?\s+(?:must|shall|should)\s+(?:use|avoid)\s+.+?\.?$/i.test(text)) {
|
|
2086
|
+
return 0;
|
|
2087
|
+
}
|
|
2088
|
+
if (!/\b(?:api|apis|code|component|computed|framework|hook|pattern|signal|schema|type)\b/i.test(text)) {
|
|
2089
|
+
return 0;
|
|
2090
|
+
}
|
|
2091
|
+
return 0.98;
|
|
2092
|
+
}
|
|
2093
|
+
if (schema.predicate_name === "migration_boundary_rule") {
|
|
2094
|
+
if (!/^.+?\s+may\s+only\s+be\s+read\s+as\s+migration\s+input(?:\s+by\s+.+?)?\.?$/i.test(text)) {
|
|
2095
|
+
return 0;
|
|
2096
|
+
}
|
|
2097
|
+
return 0.98;
|
|
2098
|
+
}
|
|
2099
|
+
if (schema.predicate_name === "absence_requirement") {
|
|
2100
|
+
if (!/^.+?\s+(?:must|shall|should)\s+be\s+(?:absent|removed)\.?$/i.test(text) &&
|
|
2101
|
+
!/^no\s+.+?\.?$/i.test(text)) {
|
|
2102
|
+
return 0;
|
|
2103
|
+
}
|
|
2104
|
+
return 0.98;
|
|
2105
|
+
}
|
|
2106
|
+
if (schema.predicate_name === "offline_behavior_rule") {
|
|
2107
|
+
if (!/^.+?\s+(?:must|shall|should)\s+be\s+(?:non-blocking|resilient)\s+during\s+offline\s+conditions\.?$/i.test(text)) {
|
|
2108
|
+
return 0;
|
|
2109
|
+
}
|
|
2110
|
+
return 0.98;
|
|
2111
|
+
}
|
|
2112
|
+
if (schema.predicate_name === "release_gate_rule") {
|
|
2113
|
+
if (!/^.+?\s+(?:must|shall|should)\s+pass\s+.+?\s+before\s+.+?\.?$/i.test(text)) {
|
|
2114
|
+
return 0;
|
|
2115
|
+
}
|
|
2116
|
+
if (!/\b(?:app store|build|deployment|distribution|release|testflight)\b/i.test(text)) {
|
|
2117
|
+
return 0;
|
|
2118
|
+
}
|
|
2119
|
+
return 0.98;
|
|
2120
|
+
}
|
|
2121
|
+
if (schema.predicate_name === "platform_consistency_rule") {
|
|
2122
|
+
if (!/^.+?\s+(?:must|shall|should)\s+synchronize\s+across\s+.+?\.?$/i.test(text)) {
|
|
2123
|
+
return 0;
|
|
2124
|
+
}
|
|
2125
|
+
return 0.98;
|
|
2126
|
+
}
|
|
2127
|
+
if (schema.predicate_name === "preservation_rule") {
|
|
2128
|
+
if (!/^.+?\s+(?:must|shall|should)\s+preserve\s+.+?\s+when\s+.+?\.?$/i.test(text)) {
|
|
2129
|
+
return 0;
|
|
2130
|
+
}
|
|
2131
|
+
return 0.98;
|
|
2132
|
+
}
|
|
2133
|
+
if (schema.predicate_name === "abstraction_boundary_rule") {
|
|
2134
|
+
if (!/^.+?\s+(?:must|shall|should)\s+be\s+persisted\s+as\s+.+?\.?$/i.test(text)) {
|
|
2135
|
+
return 0;
|
|
2136
|
+
}
|
|
2137
|
+
if (!/\b(?:neutral|contract|renderer|runtime|vendor)\b/i.test(text)) {
|
|
2138
|
+
return 0;
|
|
2139
|
+
}
|
|
2140
|
+
return 0.98;
|
|
2141
|
+
}
|
|
2142
|
+
if (schema.predicate_name === "security_configuration_rule") {
|
|
2143
|
+
if (!/^.+?\s+(?:must|shall|should)\s+have\s+explicit\s+[A-Za-z0-9_.-]+\s+[A-Za-z0-9_.-]+\.?$/i.test(text)) {
|
|
2144
|
+
return 0;
|
|
2145
|
+
}
|
|
2146
|
+
if (!/\b(?:database|deployment|function|rpc|search_path|security|trigger)\b/i.test(text)) {
|
|
2147
|
+
return 0;
|
|
2148
|
+
}
|
|
2149
|
+
return 0.98;
|
|
2150
|
+
}
|
|
2151
|
+
if (schema.predicate_name === "ordered_strategy_rule") {
|
|
2152
|
+
if (!/^.+?\s+(?:must|shall|should)\s+use\s+.+?\s+in\s+priority\s+order\s+.+?\.?$/i.test(text)) {
|
|
2153
|
+
return 0;
|
|
2154
|
+
}
|
|
2155
|
+
return 0.98;
|
|
2156
|
+
}
|
|
2157
|
+
if (schema.predicate_name === "refresh_policy_rule") {
|
|
2158
|
+
if (!/^.+?\s+(?:must|shall|should)\s+automatically\s+refresh\s+.+?\s+without\s+requiring\s+manual\s+page\s+reload\.?$/i.test(text)) {
|
|
2159
|
+
return 0;
|
|
2160
|
+
}
|
|
2161
|
+
return 0.98;
|
|
2162
|
+
}
|
|
2163
|
+
if (schema.predicate_name === "scoped_authorization_rule") {
|
|
2164
|
+
if (!/^.+?\s+(?:must|shall|should)\s+be\s+denied\s+.+?\.?$/i.test(text)) {
|
|
2165
|
+
return 0;
|
|
2166
|
+
}
|
|
2167
|
+
if (!/\b(?:assigned|unassigned|owner|member|scoped)\b/i.test(text)) {
|
|
2168
|
+
return 0;
|
|
2169
|
+
}
|
|
2170
|
+
return 0.98;
|
|
2171
|
+
}
|
|
2172
|
+
if (schema.predicate_name === "documentation_standard_rule") {
|
|
2173
|
+
if (!/^.+?\s+(?:must|shall|should)\s+be\s+documented\s+in\s+.+?\.?$/i.test(text)) {
|
|
2174
|
+
return 0;
|
|
2175
|
+
}
|
|
2176
|
+
return 0.98;
|
|
2177
|
+
}
|
|
2178
|
+
if (schema.predicate_name === "warmup_policy_rule") {
|
|
2179
|
+
if (!/^(?:the\s+)?.+?\s+(?:must|shall|should)\s+warm\s+up\s+on\s+.+?\.?$/i.test(text)) {
|
|
2180
|
+
return 0;
|
|
2181
|
+
}
|
|
2182
|
+
return 0.98;
|
|
2183
|
+
}
|
|
2184
|
+
if (schema.predicate_name === "visual_layout_rule") {
|
|
2185
|
+
if (!/^.+?\s+(?:must|shall|should)\s+remain\s+visually\s+aligned\s+with\s+.+?\.?$/i.test(text)) {
|
|
2186
|
+
return 0;
|
|
2187
|
+
}
|
|
2188
|
+
return 0.98;
|
|
2189
|
+
}
|
|
2190
|
+
if (schema.predicate_name === "enforcement_location_rule") {
|
|
2191
|
+
if (!/^.+?\s+(?:must|shall|should)\s+be\s+enforced\s+at\s+.+?\.?$/i.test(text)) {
|
|
2192
|
+
return 0;
|
|
2193
|
+
}
|
|
2194
|
+
return 0.98;
|
|
2195
|
+
}
|
|
2196
|
+
if (schema.predicate_name === "reconciliation_rule") {
|
|
2197
|
+
if (!/^on\s+.+?,\s*.+?\s+(?:must|shall|should)\s+reconcile\s+.+?\s+and\s+clear\s+stale\s+.+?\.?$/i.test(text)) {
|
|
2198
|
+
return 0;
|
|
2199
|
+
}
|
|
2200
|
+
return 0.98;
|
|
2201
|
+
}
|
|
2202
|
+
if (schema.predicate_name === "throttle_policy_rule") {
|
|
2203
|
+
if (!/^.+?\s+(?:must|shall|should)\s+be\s+throttled\s+for\s+.+?\.?$/i.test(text)) {
|
|
2204
|
+
return 0;
|
|
2205
|
+
}
|
|
2206
|
+
return 0.98;
|
|
2207
|
+
}
|
|
2208
|
+
if (schema.predicate_name === "temporal_order") {
|
|
2209
|
+
if (/^(?:the\s+)?.+?\s+initializes\s+after\s+(?:the\s+)?.+?\s+is\s+ready\.?$/i.test(text)) {
|
|
2210
|
+
return 0.98;
|
|
2211
|
+
}
|
|
2212
|
+
}
|
|
2213
|
+
if (schema.predicate_name === "state_transition") {
|
|
2214
|
+
if (!/^when\s+.+?,\s*(?:the\s+)?[a-z][a-z\s_-]*?\s+transitions?\s+from\s+[a-z][a-z0-9_-]*\s+to\s+[a-z][a-z0-9_-]*\.?$/i.test(text)) {
|
|
2215
|
+
return 0;
|
|
2216
|
+
}
|
|
2217
|
+
return 0.98;
|
|
2218
|
+
}
|
|
2219
|
+
if (schema.predicate_name === "state_transition" ||
|
|
2220
|
+
schema.predicate_name === "conditional_behavior") {
|
|
2221
|
+
return 0.98;
|
|
2222
|
+
}
|
|
2223
|
+
if (schema.predicate_name === "commit_action") {
|
|
2224
|
+
if (/\b(?:auto-save|autosave|commit|persist)\b/i.test(text)) {
|
|
2225
|
+
return 0.98;
|
|
2226
|
+
}
|
|
2227
|
+
if (/\b(?:save|saves)\b/i.test(text) &&
|
|
2228
|
+
!/\bwithout\s+save\b/i.test(text) &&
|
|
2229
|
+
!/\bsaved\s+before\b/i.test(text)) {
|
|
2230
|
+
return 0.98;
|
|
2231
|
+
}
|
|
2232
|
+
return 0;
|
|
2233
|
+
}
|
|
2234
|
+
const keywordHits = schema.keywords.filter((keyword) => matchesKeyword(text, lower, keyword)).length;
|
|
355
2235
|
if (keywordHits === 0)
|
|
356
2236
|
return 0;
|
|
357
2237
|
const normalized = keywordHits / Math.max(3, schema.keywords.length / 2);
|
|
@@ -368,6 +2248,9 @@ function schemaForCandidate(schema) {
|
|
|
368
2248
|
argument_types: schema.argument_types,
|
|
369
2249
|
examples: schema.examples,
|
|
370
2250
|
tags: schema.tags,
|
|
2251
|
+
usage_hints: schema.usage_hints ??
|
|
2252
|
+
USAGE_HINTS_BY_PREDICATE[schema.predicate_name] ??
|
|
2253
|
+
DEFAULT_USAGE_HINTS,
|
|
371
2254
|
};
|
|
372
2255
|
}
|
|
373
2256
|
async function loadExistingPredicateSchemas(prolog, includeExistingSchemas, warnings) {
|
|
@@ -398,6 +2281,7 @@ function predicateSchemaFromEntity(entity) {
|
|
|
398
2281
|
: undefined);
|
|
399
2282
|
if (!predicateName)
|
|
400
2283
|
return [];
|
|
2284
|
+
const usageHints = usageHintsFromEntity(entity);
|
|
401
2285
|
return [
|
|
402
2286
|
{
|
|
403
2287
|
id: String(entity.id ?? hashId("FACT-SCHEMA", [predicateName])),
|
|
@@ -414,9 +2298,18 @@ function predicateSchemaFromEntity(entity) {
|
|
|
414
2298
|
],
|
|
415
2299
|
examples: stringArray(entity.examples),
|
|
416
2300
|
tags: stringArray(entity.tags),
|
|
2301
|
+
...(usageHints ? { usage_hints: usageHints } : {}),
|
|
417
2302
|
},
|
|
418
2303
|
];
|
|
419
2304
|
}
|
|
2305
|
+
function usageHintsFromEntity(entity) {
|
|
2306
|
+
const useWhen = stringArray(entity.use_when);
|
|
2307
|
+
const doNotUseWhen = stringArray(entity.do_not_use_when);
|
|
2308
|
+
if (useWhen.length === 0 || doNotUseWhen.length === 0) {
|
|
2309
|
+
return undefined;
|
|
2310
|
+
}
|
|
2311
|
+
return { use_when: useWhen, do_not_use_when: doNotUseWhen };
|
|
2312
|
+
}
|
|
420
2313
|
function stringArray(value) {
|
|
421
2314
|
if (!Array.isArray(value))
|
|
422
2315
|
return [];
|
|
@@ -525,6 +2418,9 @@ export async function handleKbSuggestPredicates(prolog, args) {
|
|
|
525
2418
|
})
|
|
526
2419
|
.slice(0, maxCandidates)
|
|
527
2420
|
.map((scored) => buildSuggestion(scored.schema, text, subject, scored.score));
|
|
2421
|
+
if (candidates.length === 0) {
|
|
2422
|
+
warnings.push("No predicate candidate met minScore. If this is recurring domain language, create a fact_kind=predicate_schema fact; otherwise keep the generated review:ontology-gap observation. Do not invent unsupported predicate names without a predicate_schema.");
|
|
2423
|
+
}
|
|
528
2424
|
const recommendedAction = candidates.length > 0 ? "apply_requires_predicate" : "record_ontology_gap";
|
|
529
2425
|
const firstCandidate = candidates[0];
|
|
530
2426
|
const applyPlan = firstCandidate
|