delimit-cli 4.5.1 → 4.5.3
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/CHANGELOG.md +87 -0
- package/README.md +15 -5
- package/bin/delimit-cli.js +109 -24
- package/gateway/ai/content_engine.py +3 -4
- package/gateway/ai/inbox_classifier.py +215 -0
- package/gateway/ai/integrations/opensage_wrapper.py +4 -1
- package/gateway/ai/ledger_manager.py +218 -38
- package/gateway/ai/license.py +26 -0
- package/gateway/ai/notify.py +68 -3
- package/gateway/ai/reddit_proxy.py +93 -15
- package/gateway/ai/reddit_scanner.py +36 -18
- package/gateway/ai/remote_resolve.py +422 -0
- package/gateway/ai/server.py +301 -117
- package/gateway/ai/social_capability/__init__.py +6 -0
- package/gateway/ai/social_capability/capability_validator.py +367 -0
- package/gateway/ai/social_capability/current_capabilities.yaml +95 -0
- package/gateway/ai/social_capability/fit_floor.py +360 -0
- package/gateway/ai/social_queue.py +307 -0
- package/gateway/ai/supabase_sync.py +14 -2
- package/gateway/ai/swarm.py +29 -11
- package/gateway/ai/tui.py +6 -2
- package/gateway/ai/vendor_news/__init__.py +14 -0
- package/gateway/ai/vendor_news/drafter.py +562 -0
- package/gateway/ai/vendor_news/sensor.py +509 -0
- package/gateway/ai/vendor_news/watchlist.yaml +71 -0
- package/gateway/ai/x_ranker.py +417 -0
- package/lib/attest-mcp.js +487 -0
- package/lib/attest-telemetry.js +48 -0
- package/lib/delimit-home.js +35 -0
- package/lib/delimit-template.js +14 -0
- package/package.json +25 -3
- package/scripts/postinstall.js +89 -40
- package/adapters/codex-security.js +0 -64
- package/adapters/codex-skill.js +0 -78
- package/gateway/ai/content_grounding/__init__.py +0 -98
- package/gateway/ai/content_grounding/build.py +0 -350
- package/gateway/ai/content_grounding/consume.py +0 -280
- package/gateway/ai/content_grounding/features.py +0 -218
- package/gateway/ai/content_grounding/fixtures/fail/01_missing_evidence.json +0 -9
- package/gateway/ai/content_grounding/fixtures/fail/02_unknown_evidence_prefix.json +0 -9
- package/gateway/ai/content_grounding/fixtures/fail/03_banned_comparative.json +0 -17
- package/gateway/ai/content_grounding/fixtures/fail/04_banned_adoption.json +0 -17
- package/gateway/ai/content_grounding/fixtures/fail/05_aggregate_no_numeric.json +0 -17
- package/gateway/ai/content_grounding/fixtures/fail/06_unversioned_inference_rule.json +0 -18
- package/gateway/ai/content_grounding/fixtures/pass/01_feature_shipped.json +0 -18
- package/gateway/ai/content_grounding/fixtures/pass/02_aggregate_claim.json +0 -23
- package/gateway/ai/content_grounding/fixtures/pass/03_attestation.json +0 -16
- package/gateway/ai/content_grounding/schemas/claim.schema.json +0 -40
- package/gateway/ai/content_grounding/schemas/event.schema.json +0 -23
- package/gateway/ai/content_grounding/schemas.py +0 -276
- package/gateway/ai/content_grounding/telemetry.py +0 -221
- package/gateway/ai/inbox_drafts/__init__.py +0 -61
- package/gateway/ai/inbox_drafts/registry.py +0 -412
- package/gateway/ai/inbox_drafts/schema.py +0 -374
- package/gateway/ai/inbox_executor.py +0 -565
|
@@ -0,0 +1,367 @@
|
|
|
1
|
+
"""Capability-currency validator (LED-216 Phase 1, tightened LED-1240).
|
|
2
|
+
|
|
3
|
+
Validates social drafts against ``current_capabilities.yaml``. The validator
|
|
4
|
+
hard-fails any draft that:
|
|
5
|
+
|
|
6
|
+
* names a banned surface (literal or regex), OR
|
|
7
|
+
* mentions the Delimit product without anchoring to ground truth (no
|
|
8
|
+
canonical phrase AND no matched_claim from allowed_claims), OR
|
|
9
|
+
* mentions the Delimit product on a long-form platform (reddit, hn, devto,
|
|
10
|
+
etc.) without a delimit.ai URL anchor.
|
|
11
|
+
|
|
12
|
+
The 2026-05-05 tightening (LED-1240) was a response to founder feedback that
|
|
13
|
+
generic-claim drafts were leaking through with only a soft warning. The fix
|
|
14
|
+
reclassifies "mentions product, names no specific claim, links no artifact"
|
|
15
|
+
as a hard-fail — drafts that talk about Delimit must either (a) name a
|
|
16
|
+
mechanism from allowed_claims, or (b) link to a delimit.ai resource (the
|
|
17
|
+
methodology, a worked-example report, the attestation replay UI, etc.).
|
|
18
|
+
|
|
19
|
+
Twitter (and other ≤280-char platforms) gets a deliberate carve-out: a URL
|
|
20
|
+
won't always fit, so for ``platform="twitter"`` the URL requirement is
|
|
21
|
+
relaxed to "draft must contain at least one matched_claim". Reddit, HN,
|
|
22
|
+
devto, and any unspecified platform keep the URL requirement.
|
|
23
|
+
|
|
24
|
+
Wiring: ``ai.social.save_draft`` calls :func:`validate_draft` after the
|
|
25
|
+
existing tone/length checks but BEFORE the file is appended. On hard-fail
|
|
26
|
+
the draft's ``quality`` is overridden to ``"rejected_capability_drift"`` and
|
|
27
|
+
the entry MUST NOT be enqueued for notify. On warn the quality becomes
|
|
28
|
+
``"ready_with_warnings"``. Both outcomes are logged to
|
|
29
|
+
``~/.delimit/social_drafts_validation.jsonl`` for audit / replay.
|
|
30
|
+
|
|
31
|
+
Governance: the underlying ``current_capabilities.yaml`` is gated by the
|
|
32
|
+
LED-1037 banned-vocabulary contract — edits require a unanimous
|
|
33
|
+
``delimit_deliberate`` verdict. This module only consumes the file; it does
|
|
34
|
+
not mutate it.
|
|
35
|
+
"""
|
|
36
|
+
|
|
37
|
+
from __future__ import annotations
|
|
38
|
+
|
|
39
|
+
import json
|
|
40
|
+
import logging
|
|
41
|
+
import re
|
|
42
|
+
from datetime import datetime, timezone
|
|
43
|
+
from pathlib import Path
|
|
44
|
+
from typing import Any, Dict, List, Optional
|
|
45
|
+
|
|
46
|
+
try:
|
|
47
|
+
import yaml as _yaml
|
|
48
|
+
except ImportError: # pragma: no cover
|
|
49
|
+
_yaml = None # type: ignore[assignment]
|
|
50
|
+
|
|
51
|
+
logger = logging.getLogger("delimit.ai.social_capability.capability_validator")
|
|
52
|
+
|
|
53
|
+
# Default capabilities path — co-located with this module so the file ships
|
|
54
|
+
# (or doesn't) with the same npm exclusion rule as the rest of
|
|
55
|
+
# ai/social_capability/. The package name is `social_capability` rather
|
|
56
|
+
# than `social` because `ai/social.py` already exists as a top-level
|
|
57
|
+
# module and Python forbids a package with the same name as a sibling
|
|
58
|
+
# module.
|
|
59
|
+
DEFAULT_CAPABILITIES_PATH = Path(__file__).parent / "current_capabilities.yaml"
|
|
60
|
+
|
|
61
|
+
# Audit log for every validator decision (pass / warn / fail). One JSONL
|
|
62
|
+
# line per draft. Used by self-repair to detect over- or under-firing.
|
|
63
|
+
VALIDATION_LOG = Path.home() / ".delimit" / "social_drafts_validation.jsonl"
|
|
64
|
+
|
|
65
|
+
# ── product-mention detection ────────────────────────────────────────
|
|
66
|
+
|
|
67
|
+
# Case-insensitive match for "Delimit" as a standalone word OR the
|
|
68
|
+
# @delimit_ai twitter handle. Avoid false positives on hostnames like
|
|
69
|
+
# "delimit.ai/methodology/..." by NOT requiring word boundaries — the URL
|
|
70
|
+
# itself is a product reference, which is exactly the case where we want
|
|
71
|
+
# to nudge the canonical phrase.
|
|
72
|
+
_PRODUCT_MENTION_RE = re.compile(
|
|
73
|
+
r"(?:\bDelimit\b|@delimit_ai|delimit\.ai)",
|
|
74
|
+
re.IGNORECASE,
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
# Match a delimit.ai URL anchor — either a recognized known path, or a bare
|
|
78
|
+
# delimit.ai reference. Used for the LED-1240 long-form URL-grounding gate.
|
|
79
|
+
# The known-path list is recognised by the validator and any other
|
|
80
|
+
# delimit.ai/<segment> URL also counts as grounding (we only need to know
|
|
81
|
+
# the draft is anchored to a real artifact on the public site).
|
|
82
|
+
_DELIMIT_URL_RE = re.compile(
|
|
83
|
+
r"\bdelimit\.ai(?:/(?:methodology|reports|att|docs|trust|pricing)\b|/\S+|\b)",
|
|
84
|
+
re.IGNORECASE,
|
|
85
|
+
)
|
|
86
|
+
|
|
87
|
+
# Platforms that get the short-form URL relaxation. Anything not in this set
|
|
88
|
+
# is treated as long-form and must carry a delimit.ai URL anchor when the
|
|
89
|
+
# draft mentions Delimit by name.
|
|
90
|
+
_SHORT_FORM_PLATFORMS = {"twitter", "x"}
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def _load_capabilities(path: Path) -> Dict[str, Any]:
|
|
94
|
+
"""Load and parse the capabilities YAML.
|
|
95
|
+
|
|
96
|
+
Returns an empty config (no banned, no claims) if the file is missing
|
|
97
|
+
or YAML is unavailable, so the validator fails open in degraded
|
|
98
|
+
environments rather than crashing draft generation. The fail-open is
|
|
99
|
+
intentional: a missing capability file should NOT block legitimate
|
|
100
|
+
drafts; the worst outcome is the draft passes through without
|
|
101
|
+
capability-currency enforcement, which we'll catch via the audit log.
|
|
102
|
+
"""
|
|
103
|
+
if _yaml is None:
|
|
104
|
+
logger.warning(
|
|
105
|
+
"capability_validator: PyYAML not available; validator is a no-op"
|
|
106
|
+
)
|
|
107
|
+
return {}
|
|
108
|
+
if not path.exists():
|
|
109
|
+
logger.warning(
|
|
110
|
+
"capability_validator: capabilities file missing at %s; "
|
|
111
|
+
"validator is a no-op",
|
|
112
|
+
path,
|
|
113
|
+
)
|
|
114
|
+
return {}
|
|
115
|
+
try:
|
|
116
|
+
with open(path, "r", encoding="utf-8") as f:
|
|
117
|
+
data = _yaml.safe_load(f) or {}
|
|
118
|
+
except Exception as exc: # pragma: no cover — corrupt yaml
|
|
119
|
+
logger.error(
|
|
120
|
+
"capability_validator: failed to load %s: %s", path, exc
|
|
121
|
+
)
|
|
122
|
+
return {}
|
|
123
|
+
if not isinstance(data, dict):
|
|
124
|
+
logger.error(
|
|
125
|
+
"capability_validator: %s did not parse to a mapping", path
|
|
126
|
+
)
|
|
127
|
+
return {}
|
|
128
|
+
return data
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
def _matched_claims(text: str, claims: List[Dict[str, Any]]) -> List[str]:
|
|
132
|
+
"""Return IDs of allowed_claims whose surface_name appears in text."""
|
|
133
|
+
out: List[str] = []
|
|
134
|
+
lower = text.lower()
|
|
135
|
+
for claim in claims:
|
|
136
|
+
if not isinstance(claim, dict):
|
|
137
|
+
continue
|
|
138
|
+
surface = (claim.get("surface_name") or "").strip()
|
|
139
|
+
cid = (claim.get("id") or "").strip()
|
|
140
|
+
if not surface or not cid:
|
|
141
|
+
continue
|
|
142
|
+
if surface.lower() in lower:
|
|
143
|
+
out.append(cid)
|
|
144
|
+
return out
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
def _matched_banned_literal(
|
|
148
|
+
text: str, banned: List[str]
|
|
149
|
+
) -> List[str]:
|
|
150
|
+
"""Return banned surface literals (case-insensitive) found in text."""
|
|
151
|
+
lower = text.lower()
|
|
152
|
+
return [b for b in banned if isinstance(b, str) and b and b.lower() in lower]
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
def _matched_banned_pattern(
|
|
156
|
+
text: str, patterns: List[str]
|
|
157
|
+
) -> List[str]:
|
|
158
|
+
"""Return banned regex patterns that match somewhere in text.
|
|
159
|
+
|
|
160
|
+
Compilation errors on individual patterns are logged and the pattern is
|
|
161
|
+
skipped so one bad regex does not break the whole validator.
|
|
162
|
+
"""
|
|
163
|
+
out: List[str] = []
|
|
164
|
+
for pat in patterns:
|
|
165
|
+
if not isinstance(pat, str) or not pat:
|
|
166
|
+
continue
|
|
167
|
+
try:
|
|
168
|
+
if re.search(pat, text, flags=re.IGNORECASE):
|
|
169
|
+
out.append(pat)
|
|
170
|
+
except re.error as exc:
|
|
171
|
+
logger.warning(
|
|
172
|
+
"capability_validator: bad banned_surface_pattern %r: %s",
|
|
173
|
+
pat, exc,
|
|
174
|
+
)
|
|
175
|
+
continue
|
|
176
|
+
return out
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
def _has_canonical_phrase(text: str, phrases: List[str]) -> bool:
|
|
180
|
+
lower = text.lower()
|
|
181
|
+
for p in phrases:
|
|
182
|
+
if isinstance(p, str) and p and p.lower() in lower:
|
|
183
|
+
return True
|
|
184
|
+
return False
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
def _mentions_product(text: str) -> bool:
|
|
188
|
+
return bool(_PRODUCT_MENTION_RE.search(text or ""))
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
def _has_delimit_url(text: str) -> bool:
|
|
192
|
+
"""True iff the draft contains any delimit.ai URL anchor.
|
|
193
|
+
|
|
194
|
+
Matches both the curated path list (delimit.ai/methodology, /reports,
|
|
195
|
+
/att, /docs, /trust, /pricing) and any other delimit.ai/<path> URL.
|
|
196
|
+
Bare 'delimit.ai' is also accepted — the goal is grounding, not exact
|
|
197
|
+
path validation.
|
|
198
|
+
"""
|
|
199
|
+
return bool(_DELIMIT_URL_RE.search(text or ""))
|
|
200
|
+
|
|
201
|
+
|
|
202
|
+
def _append_audit(record: Dict[str, Any]) -> None:
|
|
203
|
+
"""Append a validation decision to the audit log. Best-effort."""
|
|
204
|
+
try:
|
|
205
|
+
VALIDATION_LOG.parent.mkdir(parents=True, exist_ok=True)
|
|
206
|
+
with open(VALIDATION_LOG, "a", encoding="utf-8") as f:
|
|
207
|
+
f.write(json.dumps(record) + "\n")
|
|
208
|
+
except Exception as exc: # pragma: no cover — disk full, etc.
|
|
209
|
+
logger.debug("capability_validator: audit write failed: %s", exc)
|
|
210
|
+
|
|
211
|
+
|
|
212
|
+
def validate_draft(
|
|
213
|
+
text: str,
|
|
214
|
+
capabilities_path: Optional[Path] = None,
|
|
215
|
+
*,
|
|
216
|
+
platform: str = "",
|
|
217
|
+
audit_meta: Optional[Dict[str, Any]] = None,
|
|
218
|
+
log: bool = True,
|
|
219
|
+
) -> Dict[str, Any]:
|
|
220
|
+
"""Validate a social draft against ``current_capabilities.yaml``.
|
|
221
|
+
|
|
222
|
+
Args:
|
|
223
|
+
text: The candidate draft text.
|
|
224
|
+
capabilities_path: Override path to the capabilities YAML. Defaults
|
|
225
|
+
to the bundled ``current_capabilities.yaml`` next to this
|
|
226
|
+
module.
|
|
227
|
+
platform: Platform string ("twitter", "reddit", "hn", "devto", ...).
|
|
228
|
+
Twitter / X get a short-form carve-out: drafts that mention the
|
|
229
|
+
product without a delimit.ai URL still pass IF they cite a
|
|
230
|
+
specific allowed_claim. Long-form platforms must carry both a
|
|
231
|
+
specific claim AND a URL anchor when they mention the product.
|
|
232
|
+
Empty string defaults to long-form behavior (strictest gate).
|
|
233
|
+
audit_meta: Optional fields to embed in the audit log entry
|
|
234
|
+
(e.g. ``{"draft_id": ..., "platform": ...}``). Never required
|
|
235
|
+
for validation logic.
|
|
236
|
+
log: When False, skip the audit log write. Used by tests.
|
|
237
|
+
|
|
238
|
+
Returns:
|
|
239
|
+
Dict with:
|
|
240
|
+
- ``ok`` (bool): False iff a banned surface (literal or pattern)
|
|
241
|
+
appeared, OR the draft mentions the product but anchors to no
|
|
242
|
+
ground truth (LED-1240).
|
|
243
|
+
- ``errors`` (list[str]): Hard-fail reasons.
|
|
244
|
+
- ``warnings`` (list[str]): Soft-fail reasons.
|
|
245
|
+
- ``matched_claims`` (list[str]): IDs of allowed_claims found.
|
|
246
|
+
- ``matched_banned`` (list[str]): Banned surfaces / patterns hit.
|
|
247
|
+
- ``mentions_product`` (bool): Whether the draft references
|
|
248
|
+
Delimit by name or handle.
|
|
249
|
+
- ``has_canonical_phrase`` (bool): Whether at least one canonical
|
|
250
|
+
phrase appears.
|
|
251
|
+
- ``has_delimit_url`` (bool): Whether the draft contains a
|
|
252
|
+
delimit.ai URL anchor.
|
|
253
|
+
- ``platform`` (str): The normalized platform string used for the
|
|
254
|
+
short-form carve-out decision.
|
|
255
|
+
"""
|
|
256
|
+
text = text or ""
|
|
257
|
+
path = capabilities_path or DEFAULT_CAPABILITIES_PATH
|
|
258
|
+
cfg = _load_capabilities(path)
|
|
259
|
+
|
|
260
|
+
allowed_claims = cfg.get("allowed_claims") or []
|
|
261
|
+
banned_literals = cfg.get("banned_surfaces") or []
|
|
262
|
+
banned_patterns = cfg.get("banned_surface_patterns") or []
|
|
263
|
+
required_phrases = cfg.get("required_canonical_phrases") or []
|
|
264
|
+
|
|
265
|
+
matched_claims = _matched_claims(text, allowed_claims)
|
|
266
|
+
matched_literal = _matched_banned_literal(text, banned_literals)
|
|
267
|
+
matched_patterns = _matched_banned_pattern(text, banned_patterns)
|
|
268
|
+
matched_banned = matched_literal + matched_patterns
|
|
269
|
+
|
|
270
|
+
errors: List[str] = []
|
|
271
|
+
for hit in matched_literal:
|
|
272
|
+
errors.append(
|
|
273
|
+
f"banned surface literal: {hit!r} — see ai/social_capability/current_capabilities.yaml"
|
|
274
|
+
)
|
|
275
|
+
for pat in matched_patterns:
|
|
276
|
+
errors.append(
|
|
277
|
+
f"banned surface pattern matched: {pat!r} (tool-count hero "
|
|
278
|
+
f"language is forbidden in social copy)"
|
|
279
|
+
)
|
|
280
|
+
|
|
281
|
+
mentions_product = _mentions_product(text)
|
|
282
|
+
has_canonical = _has_canonical_phrase(text, required_phrases)
|
|
283
|
+
has_url = _has_delimit_url(text)
|
|
284
|
+
platform_norm = (platform or "").strip().lower()
|
|
285
|
+
is_short_form = platform_norm in _SHORT_FORM_PLATFORMS
|
|
286
|
+
|
|
287
|
+
warnings: List[str] = []
|
|
288
|
+
|
|
289
|
+
# ── LED-1240 grounding gate ──────────────────────────────────────
|
|
290
|
+
# Tightened 2026-05-05: a draft that mentions Delimit but anchors to
|
|
291
|
+
# no ground truth is now a hard-fail, not a soft warning. The two
|
|
292
|
+
# rules are:
|
|
293
|
+
# (a) Product mention + no canonical phrase + no matched_claim
|
|
294
|
+
# ⇒ hard-fail on every platform. The draft is naming the
|
|
295
|
+
# product without grounding to anything in the allow list.
|
|
296
|
+
# (b) Product mention on a long-form platform without a delimit.ai
|
|
297
|
+
# URL anchor ⇒ hard-fail. Twitter / X are exempt because a URL
|
|
298
|
+
# won't always fit in 280 chars; for them, a matched_claim is
|
|
299
|
+
# sufficient grounding.
|
|
300
|
+
# The carve-out preserves the existing twitter draft contract while
|
|
301
|
+
# raising the floor for reddit / HN / devto / etc.
|
|
302
|
+
if mentions_product and required_phrases:
|
|
303
|
+
if not has_canonical and not matched_claims:
|
|
304
|
+
errors.append(
|
|
305
|
+
"draft mentions Delimit but cites no canonical phrase and no "
|
|
306
|
+
"specific allowed_claim. Anchor the claim to a mechanism in "
|
|
307
|
+
"current_capabilities.yaml or rewrite without naming the "
|
|
308
|
+
"product (LED-1240)."
|
|
309
|
+
)
|
|
310
|
+
elif not is_short_form and not has_url:
|
|
311
|
+
errors.append(
|
|
312
|
+
"draft mentions Delimit on a long-form platform "
|
|
313
|
+
f"(platform={platform_norm or 'unspecified'}) without a "
|
|
314
|
+
"delimit.ai URL anchor. Cite a specific artifact "
|
|
315
|
+
"(delimit.ai/methodology, /reports, /att, ...) so the claim "
|
|
316
|
+
"is verifiable (LED-1240)."
|
|
317
|
+
)
|
|
318
|
+
elif not has_canonical:
|
|
319
|
+
# Has a matched_claim but still missing a canonical phrase —
|
|
320
|
+
# downgrade to a warning (existing soft-fail behavior). The
|
|
321
|
+
# claim is grounded; the framing isn't on-message yet.
|
|
322
|
+
warnings.append(
|
|
323
|
+
"draft mentions Delimit and cites a specific claim but does "
|
|
324
|
+
"not include a canonical phrase (merge gate / signed, "
|
|
325
|
+
"replayable attestation / AI-written code / AI-assisted "
|
|
326
|
+
"merge). Founder review recommended."
|
|
327
|
+
)
|
|
328
|
+
|
|
329
|
+
ok = not errors
|
|
330
|
+
|
|
331
|
+
result: Dict[str, Any] = {
|
|
332
|
+
"ok": ok,
|
|
333
|
+
"errors": errors,
|
|
334
|
+
"warnings": warnings,
|
|
335
|
+
"matched_claims": matched_claims,
|
|
336
|
+
"matched_banned": matched_banned,
|
|
337
|
+
"mentions_product": mentions_product,
|
|
338
|
+
"has_canonical_phrase": has_canonical,
|
|
339
|
+
"has_delimit_url": has_url,
|
|
340
|
+
"platform": platform_norm,
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
if log:
|
|
344
|
+
record = {
|
|
345
|
+
"ts": datetime.now(timezone.utc).isoformat(),
|
|
346
|
+
"ok": ok,
|
|
347
|
+
"errors": errors,
|
|
348
|
+
"warnings": warnings,
|
|
349
|
+
"matched_claims": matched_claims,
|
|
350
|
+
"matched_banned": matched_banned,
|
|
351
|
+
"mentions_product": mentions_product,
|
|
352
|
+
"has_canonical_phrase": has_canonical,
|
|
353
|
+
"has_delimit_url": has_url,
|
|
354
|
+
"platform": platform_norm,
|
|
355
|
+
"text_len": len(text),
|
|
356
|
+
"capabilities_path": str(path),
|
|
357
|
+
}
|
|
358
|
+
if audit_meta:
|
|
359
|
+
# Don't let audit_meta clobber computed fields.
|
|
360
|
+
for k, v in audit_meta.items():
|
|
361
|
+
record.setdefault(k, v)
|
|
362
|
+
_append_audit(record)
|
|
363
|
+
|
|
364
|
+
return result
|
|
365
|
+
|
|
366
|
+
|
|
367
|
+
__all__ = ["validate_draft", "DEFAULT_CAPABILITIES_PATH", "VALIDATION_LOG"]
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
# LED-216 Phase 1 — Capability Currency
|
|
2
|
+
#
|
|
3
|
+
# This file is the single source of truth for what Delimit is allowed to
|
|
4
|
+
# claim in any social draft. The capability_validator.py module reads it
|
|
5
|
+
# at draft emit time and hard-fails any draft that:
|
|
6
|
+
#
|
|
7
|
+
# * names a banned surface (literal substring), OR
|
|
8
|
+
# * matches a banned surface regex (e.g. tool-count hero language).
|
|
9
|
+
#
|
|
10
|
+
# Drafts that mention "Delimit" / "@delimit_ai" but reference NO canonical
|
|
11
|
+
# phrase are warned (not failed) so the founder can still review.
|
|
12
|
+
#
|
|
13
|
+
# ──────────────────────────────────────────────────────────────────────
|
|
14
|
+
# GOVERNANCE (mirror of LED-1037 banned-vocabulary gate)
|
|
15
|
+
# ──────────────────────────────────────────────────────────────────────
|
|
16
|
+
# Edits to this file MUST be gated by `delimit_deliberate` unanimous
|
|
17
|
+
# verdict. Same governance contract as the LED-1037 banned-vocabulary
|
|
18
|
+
# list: any change requires a multi-model panel sign-off (Claude +
|
|
19
|
+
# Gemini + Codex + Grok) before it lands. Silent edits are a policy
|
|
20
|
+
# violation and will be reverted.
|
|
21
|
+
#
|
|
22
|
+
# 30-day review cadence: capabilities rot fast. The `last_reviewed`
|
|
23
|
+
# field below MUST be bumped on every panel-approved edit, and the
|
|
24
|
+
# orchestrator should re-deliberate if `now - last_reviewed >
|
|
25
|
+
# review_cadence_days`.
|
|
26
|
+
# ──────────────────────────────────────────────────────────────────────
|
|
27
|
+
|
|
28
|
+
version: 1
|
|
29
|
+
last_reviewed: 2026-05-02
|
|
30
|
+
review_cadence_days: 30
|
|
31
|
+
|
|
32
|
+
allowed_claims:
|
|
33
|
+
# Each entry: {id, surface_name, description, since_version, evidence_link}
|
|
34
|
+
- id: attest_mcp
|
|
35
|
+
surface_name: "delimit attest mcp"
|
|
36
|
+
description: "Local CLI subcommand that runs the 5-check methodology preview."
|
|
37
|
+
since_version: 4.5.0
|
|
38
|
+
evidence_link: https://delimit.ai/methodology/mcp-attestation
|
|
39
|
+
- id: self_repair_v1
|
|
40
|
+
surface_name: "self-repair v1 closed loop"
|
|
41
|
+
description: "Sensor → diagnose → deliberate → apply → verify with founder approval gate."
|
|
42
|
+
since_version: gateway-2026-05-01
|
|
43
|
+
evidence_link: https://delimit.ai/methodology/mcp-attestation
|
|
44
|
+
- id: merge_gate
|
|
45
|
+
surface_name: "merge gate for AI-written code"
|
|
46
|
+
description: "Signed, replayable attestation for every AI-assisted merge."
|
|
47
|
+
since_version: "1.0"
|
|
48
|
+
evidence_link: https://delimit.ai
|
|
49
|
+
- id: multi_model_deliberation
|
|
50
|
+
surface_name: "multi-model deliberation panel"
|
|
51
|
+
description: "delimit_deliberate runs Claude + Gemini + Codex + Grok to consensus."
|
|
52
|
+
since_version: "1.0"
|
|
53
|
+
- id: persistent_context
|
|
54
|
+
surface_name: "persistent context across sessions"
|
|
55
|
+
description: "Memory + ledger survive across sessions and models via delimit_revive / delimit_session_handoff."
|
|
56
|
+
- id: governance_kernel
|
|
57
|
+
surface_name: "fail-closed governance kernel"
|
|
58
|
+
description: "Audit trail + evidence_collect at every gate."
|
|
59
|
+
- id: diff_engine
|
|
60
|
+
surface_name: "27 breaking-change types"
|
|
61
|
+
description: "Deterministic diff engine for OpenAPI spec changes."
|
|
62
|
+
- id: github_action
|
|
63
|
+
surface_name: "delimit-ai/delimit-action GitHub Action"
|
|
64
|
+
description: "On Marketplace, breaking-change detection on PRs."
|
|
65
|
+
- id: corp_dashboard
|
|
66
|
+
surface_name: "delimit_corp_dashboard"
|
|
67
|
+
description: "Single-call corp status synthesis."
|
|
68
|
+
since_version: gateway-2026-05-01
|
|
69
|
+
- id: methodology_v1
|
|
70
|
+
surface_name: "MCP attestation methodology v1"
|
|
71
|
+
description: "Public methodology document at delimit.ai/methodology/mcp-attestation."
|
|
72
|
+
|
|
73
|
+
banned_surfaces:
|
|
74
|
+
# Strings that MUST NOT appear in any social draft. Validator hard-fails.
|
|
75
|
+
- "AI OS"
|
|
76
|
+
- "governance platform"
|
|
77
|
+
- "one workspace for every AI coding assistant"
|
|
78
|
+
- "5 capability domains"
|
|
79
|
+
- "Jamsons OS"
|
|
80
|
+
- "Delimit OS"
|
|
81
|
+
- "186 tools"
|
|
82
|
+
|
|
83
|
+
banned_surface_patterns:
|
|
84
|
+
# Tool-count hero language patterns — match by regex in validator.
|
|
85
|
+
- "\\d+ tools"
|
|
86
|
+
- "\\d+ capability domains"
|
|
87
|
+
|
|
88
|
+
required_canonical_phrases:
|
|
89
|
+
# At least one of these MUST appear in any draft that mentions Delimit's
|
|
90
|
+
# product. If draft mentions Delimit by name and references no canonical
|
|
91
|
+
# phrase, validator warns (does not hard-fail).
|
|
92
|
+
- "merge gate"
|
|
93
|
+
- "signed, replayable attestation"
|
|
94
|
+
- "AI-written code"
|
|
95
|
+
- "AI-assisted merge"
|