delimit-cli 4.4.0 → 4.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +49 -0
- package/gateway/ai/backends/memory_bridge.py +218 -3
- package/gateway/ai/backends/tools_infra.py +10 -3
- package/gateway/ai/content_grounding/consume.py +1 -1
- package/gateway/ai/inbox_drafts/__init__.py +61 -0
- package/gateway/ai/inbox_drafts/registry.py +412 -0
- package/gateway/ai/inbox_drafts/schema.py +374 -0
- package/gateway/ai/inbox_executor.py +565 -0
- package/gateway/ai/ledger_manager.py +1474 -23
- package/gateway/ai/server.py +424 -9
- package/gateway/core/diff_engine_v2.py +45 -10
- package/gateway/core/zero_spec/express_extractor.py +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,54 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
|
|
4
|
+
## [4.5.0] - 2026-04-27
|
|
5
|
+
|
|
6
|
+
### Added — Ledger hygiene toolkit (LED-1145, 7 PRs)
|
|
7
|
+
|
|
8
|
+
The full hygiene loop is now a single MCP surface — call `delimit_ledger_health` to see what's wrong, then run the suggested tool to fix it.
|
|
9
|
+
|
|
10
|
+
- **`delimit_ledger_health`** — one-shot traffic-light status (P0 inflation / stale / duplicates / garbage venture) with ranked next-action list
|
|
11
|
+
- **`delimit_ledger_groom`** — read-only proposal: stale-open + duplicate-titles + garbage-venture detection. Each proposal includes a copy-pasteable `delimit_ledger_bulk` invocation
|
|
12
|
+
- **`delimit_ledger_bulk(item_ids, action, dry_run=True)`** — batch operations (`archive`, `set_status`, `set_priority`, `add_tag`, `mark_done`, `cancel`). `dry_run=True` is the safety default. **No hard delete** — archive is an append-only soft transition
|
|
13
|
+
- **`delimit_ledger_auto_close_external`** — find LEDs linked to a GitHub issue/PR and auto-close when the upstream resolves (mark_done for merged PRs / closed-as-completed; archive for not_planned closures)
|
|
14
|
+
- **`delimit_ledger_auto_cancel_stale`** — auto-archive items dormant past `DELIMIT_STALE_TTL_DAYS` (default 60). Composes `bulk_action(archive)`. dry_run default
|
|
15
|
+
- **Extended `delimit_ledger_list`** — new filters: `status_in`, `priority_in`, `tags_contains_all`, `text`, `linked_external_id`, `created_before/after`, `updated_before/after`, `sort`, `order`, `fields` projection (`"slim"` / explicit list / unknown=error), cursor pagination
|
|
16
|
+
- **P0 soft-quota** on `delimit_ledger_add` — soft warning when count > `DELIMIT_P0_SOFT_QUOTA` (default 50). Item still added; surfaces a nudge to groom
|
|
17
|
+
|
|
18
|
+
### Added — Memory-system convergence (LED-1165)
|
|
19
|
+
|
|
20
|
+
`delimit_memory` is now the canonical durable memory; Claude Code auto-memory becomes a one-way client projection.
|
|
21
|
+
|
|
22
|
+
- **`hot_load: bool` parameter** on `delimit_memory_store` — opt-in, default False. Marks an entry for projection
|
|
23
|
+
- **`delimit_memory_index(target_path, dry_run, limit)`** — projects hot_load=True entries into a managed section of MEMORY.md (`<!-- delimit:start -->` / `<!-- delimit:end -->` markers). User content outside markers is preserved verbatim. **One-way only** — never reads MEMORY.md back into delimit_memory
|
|
24
|
+
|
|
25
|
+
### Fixed — Secret-scanner false positives in `tools_infra`
|
|
26
|
+
|
|
27
|
+
The `_CREDENTIAL_FALSE_POSITIVES` regex now suppresses three additional benign patterns so legitimate credential-loading code (env-var lookup, dict-getter, control-flow guards) doesn't trip the audit:
|
|
28
|
+
- `\w+\.get(` (any object-method getter, was tokens-only)
|
|
29
|
+
- `if not <var>:` (Python control-flow with credential variable)
|
|
30
|
+
- `:\n` matched-text (block-opener colon, not key-value separator)
|
|
31
|
+
|
|
32
|
+
### Fixed — OpenAPI diff engine defensive coverage
|
|
33
|
+
|
|
34
|
+
Real-world specs can ship malformed shapes. The diff engine now defends against the entire dict-iteration crash class without losing any actual finding:
|
|
35
|
+
|
|
36
|
+
- **`required: bool` in object schemas** (legal in parameter objects but seen leaking into nested schemas) — was raising `TypeError: 'bool' object is not iterable`. Treats as no-required-fields and continues
|
|
37
|
+
- **`properties: [...]` instead of dict** (Kong-class) — `.keys()` no longer crashes; treats as empty properties and continues
|
|
38
|
+
- **`paths: []`, `responses: []`, `content: []` (request and response)** — all coerce to `{}` at function entry. Diffs against the well-formed side still produce correct findings
|
|
39
|
+
|
|
40
|
+
### Tests
|
|
41
|
+
- 108 ledger-manager tests (15 originals + 93 new across 7 features + E2E hygiene-loop integration)
|
|
42
|
+
- 24 memory-bridge tests (new test file)
|
|
43
|
+
- 60 diff-engine tests (49 + 11 new defensive)
|
|
44
|
+
- All backward-compatible — existing test suites pass without modification
|
|
45
|
+
|
|
46
|
+
### Backward compatibility
|
|
47
|
+
- All MCP tool parameter additions are optional with safe defaults
|
|
48
|
+
- Existing storage format is unchanged (`hot_load` and `archived` are additive on the entry/status side)
|
|
49
|
+
- No CLI command renamed or removed
|
|
50
|
+
- Default `delimit_ledger_list` response shape preserved (full record by default; `fields="slim"` opts into the 90% payload reduction)
|
|
51
|
+
|
|
3
52
|
## [4.4.0] - 2026-04-25
|
|
4
53
|
|
|
5
54
|
### Added — Pre-external-PR duplicate guard
|
|
@@ -19,8 +19,28 @@ def _ensure_dir():
|
|
|
19
19
|
MEMORY_DIR.mkdir(parents=True, exist_ok=True)
|
|
20
20
|
|
|
21
21
|
|
|
22
|
-
def store(
|
|
23
|
-
|
|
22
|
+
def store(
|
|
23
|
+
content: str,
|
|
24
|
+
tags: Optional[list] = None,
|
|
25
|
+
context: Optional[str] = None,
|
|
26
|
+
hot_load: bool = False,
|
|
27
|
+
) -> Dict[str, Any]:
|
|
28
|
+
"""Store a memory entry.
|
|
29
|
+
|
|
30
|
+
LED-1165 Phase 2 #5 PR-A: opt-in `hot_load` flag marks an entry for
|
|
31
|
+
one-way projection into the Claude Code auto-memory `MEMORY.md` file
|
|
32
|
+
(managed-section). The projection writer is shipped in PR-B; this PR
|
|
33
|
+
only persists the flag.
|
|
34
|
+
|
|
35
|
+
Args:
|
|
36
|
+
content: The content to remember.
|
|
37
|
+
tags: Optional categorization tags.
|
|
38
|
+
context: Optional context about when/why this was stored.
|
|
39
|
+
hot_load: When True, mark the entry for projection into the
|
|
40
|
+
Claude Code MEMORY.md hot-load index. Default False — entries
|
|
41
|
+
are durable in delimit_memory but not projected. Existing
|
|
42
|
+
entries are unaffected (treated as hot_load=False).
|
|
43
|
+
"""
|
|
24
44
|
_ensure_dir()
|
|
25
45
|
|
|
26
46
|
# Generate ID from content hash
|
|
@@ -33,12 +53,18 @@ def store(content: str, tags: Optional[list] = None, context: Optional[str] = No
|
|
|
33
53
|
"tags": tags or [],
|
|
34
54
|
"context": context or "",
|
|
35
55
|
"created_at": ts,
|
|
56
|
+
"hot_load": bool(hot_load),
|
|
36
57
|
}
|
|
37
58
|
|
|
38
59
|
path = MEMORY_DIR / f"{mem_id}.json"
|
|
39
60
|
path.write_text(json.dumps(entry, indent=2))
|
|
40
61
|
|
|
41
|
-
return {
|
|
62
|
+
return {
|
|
63
|
+
"stored": mem_id,
|
|
64
|
+
"path": str(path),
|
|
65
|
+
"created_at": ts,
|
|
66
|
+
"hot_load": bool(hot_load),
|
|
67
|
+
}
|
|
42
68
|
|
|
43
69
|
|
|
44
70
|
def search(query: str, limit: int = 10) -> Dict[str, Any]:
|
|
@@ -88,8 +114,197 @@ def get_recent(limit: int = 5) -> Dict[str, Any]:
|
|
|
88
114
|
"content": entry.get("content", "")[:500],
|
|
89
115
|
"tags": entry.get("tags", []),
|
|
90
116
|
"created_at": entry.get("created_at", ""),
|
|
117
|
+
"hot_load": bool(entry.get("hot_load", False)),
|
|
118
|
+
})
|
|
119
|
+
except Exception:
|
|
120
|
+
pass
|
|
121
|
+
|
|
122
|
+
return {"results": entries, "count": len(entries)}
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
def list_hot(limit: int = 200) -> Dict[str, Any]:
|
|
126
|
+
"""Return all entries marked hot_load=True, newest first.
|
|
127
|
+
|
|
128
|
+
LED-1165 Phase 2 #5 PR-A: backing query for the MEMORY.md projection
|
|
129
|
+
writer that PR-B will introduce. Returned entries are full content
|
|
130
|
+
(not truncated) so the projection writer can render them faithfully.
|
|
131
|
+
|
|
132
|
+
Args:
|
|
133
|
+
limit: cap on entries returned. Default 200; the projection
|
|
134
|
+
writer hard-caps the rendered MEMORY.md size so this is
|
|
135
|
+
mostly belt-and-braces.
|
|
136
|
+
|
|
137
|
+
Returns:
|
|
138
|
+
{
|
|
139
|
+
"results": [{id, content, tags, context, created_at, hot_load}, ...],
|
|
140
|
+
"count": int,
|
|
141
|
+
}
|
|
142
|
+
"""
|
|
143
|
+
_ensure_dir()
|
|
144
|
+
entries = []
|
|
145
|
+
|
|
146
|
+
for f in sorted(MEMORY_DIR.glob("*.json"), key=lambda p: p.stat().st_mtime, reverse=True):
|
|
147
|
+
if len(entries) >= limit:
|
|
148
|
+
break
|
|
149
|
+
try:
|
|
150
|
+
entry = json.loads(f.read_text())
|
|
151
|
+
if not entry.get("hot_load"):
|
|
152
|
+
continue
|
|
153
|
+
entries.append({
|
|
154
|
+
"id": entry.get("id", f.stem),
|
|
155
|
+
"content": entry.get("content", ""),
|
|
156
|
+
"tags": entry.get("tags", []),
|
|
157
|
+
"context": entry.get("context", ""),
|
|
158
|
+
"created_at": entry.get("created_at", ""),
|
|
159
|
+
"hot_load": True,
|
|
91
160
|
})
|
|
92
161
|
except Exception:
|
|
93
162
|
pass
|
|
94
163
|
|
|
95
164
|
return {"results": entries, "count": len(entries)}
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
# ── LED-1165 Phase 2 #5 PR-B: MEMORY.md projection writer ──────────────
|
|
168
|
+
|
|
169
|
+
# Default target — Claude Code's auto-memory MEMORY.md for the current
|
|
170
|
+
# project (this is where Claude Code reads memory entries on session
|
|
171
|
+
# start). Override via env or function arg for testing / non-default
|
|
172
|
+
# project layouts.
|
|
173
|
+
DEFAULT_MEMORY_MD = Path.home() / ".claude" / "projects" / "-root" / "memory" / "MEMORY.md"
|
|
174
|
+
|
|
175
|
+
PROJECTION_START_MARKER = "<!-- delimit:start -->"
|
|
176
|
+
PROJECTION_END_MARKER = "<!-- delimit:end -->"
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
def _format_hot_entry_as_markdown(entry: Dict[str, Any]) -> str:
|
|
180
|
+
"""Render one delimit_memory entry as a Markdown bullet for the
|
|
181
|
+
MEMORY.md hot-load index. Intentionally compact — the index loads
|
|
182
|
+
into every Claude Code session, so each entry should fit on a
|
|
183
|
+
line or two."""
|
|
184
|
+
mid = entry.get("id", "?")
|
|
185
|
+
content = (entry.get("content") or "").strip()
|
|
186
|
+
# Single-line the content but cap at ~280 chars so the line stays
|
|
187
|
+
# readable. Long content stays in delimit_memory; index is just
|
|
188
|
+
# the hook for Claude to know it exists.
|
|
189
|
+
one_line = " ".join(content.split())
|
|
190
|
+
if len(one_line) > 280:
|
|
191
|
+
one_line = one_line[:277] + "..."
|
|
192
|
+
tags = entry.get("tags") or []
|
|
193
|
+
tag_str = (" [tags: " + ", ".join(tags) + "]") if tags else ""
|
|
194
|
+
ctx = (entry.get("context") or "").strip()
|
|
195
|
+
ctx_line = f"\n > {ctx[:200]}" if ctx else ""
|
|
196
|
+
return f"- **{mid}**{tag_str} — {one_line}{ctx_line}"
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
def _render_managed_block(entries: List[Dict[str, Any]]) -> str:
|
|
200
|
+
"""Render the full managed section (between markers) for the
|
|
201
|
+
MEMORY.md hot-load index. Includes a one-line preamble explaining
|
|
202
|
+
the section so anyone editing the file understands what it is."""
|
|
203
|
+
if not entries:
|
|
204
|
+
body = (
|
|
205
|
+
"_No hot-load memory entries. Add one with "
|
|
206
|
+
"`delimit_memory_store(content=\"...\", hot_load=True)`._\n"
|
|
207
|
+
)
|
|
208
|
+
else:
|
|
209
|
+
bullets = "\n".join(_format_hot_entry_as_markdown(e) for e in entries)
|
|
210
|
+
body = bullets + "\n"
|
|
211
|
+
|
|
212
|
+
# Brief header + body + caveat
|
|
213
|
+
header = (
|
|
214
|
+
"## Delimit hot memory (auto-projected from delimit_memory)\n"
|
|
215
|
+
"\n"
|
|
216
|
+
f"_Auto-managed by `delimit_memory_index` — projection of {len(entries)} "
|
|
217
|
+
"entry/entries flagged `hot_load=True`. Edits inside this block are "
|
|
218
|
+
"overwritten on next projection. Add an entry via "
|
|
219
|
+
"`delimit_memory_store(content=\"...\", hot_load=True)`._\n"
|
|
220
|
+
"\n"
|
|
221
|
+
)
|
|
222
|
+
return PROJECTION_START_MARKER + "\n" + header + body + PROJECTION_END_MARKER
|
|
223
|
+
|
|
224
|
+
|
|
225
|
+
def project_to_memory_md(
|
|
226
|
+
target_path: Optional[Path] = None,
|
|
227
|
+
dry_run: bool = False,
|
|
228
|
+
limit: int = 200,
|
|
229
|
+
) -> Dict[str, Any]:
|
|
230
|
+
"""One-way projection: render hot_load=True entries from delimit_memory
|
|
231
|
+
into a managed section of Claude Code's MEMORY.md.
|
|
232
|
+
|
|
233
|
+
LED-1165 Phase 2 #5 PR-B. Composes PR-A's `list_hot` helper with a
|
|
234
|
+
managed-section markdown writer. NEVER reads MEMORY.md back into
|
|
235
|
+
delimit_memory — that's the explicit deliberation rule (Anthropic
|
|
236
|
+
owns auto-memory's format; we don't risk drift).
|
|
237
|
+
|
|
238
|
+
Behavior:
|
|
239
|
+
- If target_path's file does NOT exist, create it with just the
|
|
240
|
+
managed section (no other content).
|
|
241
|
+
- If target_path's file exists and contains markers, replace the
|
|
242
|
+
section between them. Content outside markers is preserved.
|
|
243
|
+
- If target_path's file exists but has no markers, APPEND a new
|
|
244
|
+
managed section to the end. Does NOT touch existing content.
|
|
245
|
+
|
|
246
|
+
Args:
|
|
247
|
+
target_path: where to write. Default DEFAULT_MEMORY_MD.
|
|
248
|
+
dry_run: True returns the rendered content without writing.
|
|
249
|
+
limit: cap on entries projected. Default 200 (matches list_hot).
|
|
250
|
+
|
|
251
|
+
Returns:
|
|
252
|
+
{
|
|
253
|
+
"target": str,
|
|
254
|
+
"dry_run": bool,
|
|
255
|
+
"entries": int,
|
|
256
|
+
"wrote_chars": int (or "would_write_chars"),
|
|
257
|
+
"had_existing_block": bool,
|
|
258
|
+
"had_existing_file": bool,
|
|
259
|
+
"preserved_user_content": bool,
|
|
260
|
+
}
|
|
261
|
+
"""
|
|
262
|
+
if target_path is None:
|
|
263
|
+
target_path = DEFAULT_MEMORY_MD
|
|
264
|
+
|
|
265
|
+
target_path = Path(target_path)
|
|
266
|
+
hot = list_hot(limit=limit)
|
|
267
|
+
entries = hot.get("results", [])
|
|
268
|
+
|
|
269
|
+
block = _render_managed_block(entries)
|
|
270
|
+
|
|
271
|
+
had_existing_file = target_path.exists()
|
|
272
|
+
had_existing_block = False
|
|
273
|
+
preserved_user_content = False
|
|
274
|
+
|
|
275
|
+
if had_existing_file:
|
|
276
|
+
existing = target_path.read_text()
|
|
277
|
+
start_idx = existing.find(PROJECTION_START_MARKER)
|
|
278
|
+
end_idx = existing.find(PROJECTION_END_MARKER)
|
|
279
|
+
if start_idx != -1 and end_idx != -1 and end_idx > start_idx:
|
|
280
|
+
had_existing_block = True
|
|
281
|
+
preserved_user_content = bool(
|
|
282
|
+
existing[:start_idx].strip() or existing[end_idx + len(PROJECTION_END_MARKER):].strip()
|
|
283
|
+
)
|
|
284
|
+
new_content = (
|
|
285
|
+
existing[:start_idx]
|
|
286
|
+
+ block
|
|
287
|
+
+ existing[end_idx + len(PROJECTION_END_MARKER):]
|
|
288
|
+
)
|
|
289
|
+
else:
|
|
290
|
+
# No markers — append to end, preserving everything above
|
|
291
|
+
preserved_user_content = bool(existing.strip())
|
|
292
|
+
sep = "" if existing.endswith("\n\n") else ("\n" if existing.endswith("\n") else "\n\n")
|
|
293
|
+
new_content = existing + sep + block + "\n"
|
|
294
|
+
else:
|
|
295
|
+
# Brand new file — just the managed section
|
|
296
|
+
new_content = block + "\n"
|
|
297
|
+
|
|
298
|
+
if not dry_run:
|
|
299
|
+
target_path.parent.mkdir(parents=True, exist_ok=True)
|
|
300
|
+
target_path.write_text(new_content)
|
|
301
|
+
|
|
302
|
+
return {
|
|
303
|
+
"target": str(target_path),
|
|
304
|
+
"dry_run": dry_run,
|
|
305
|
+
"entries": len(entries),
|
|
306
|
+
"wrote_chars" if not dry_run else "would_write_chars": len(new_content),
|
|
307
|
+
"had_existing_block": had_existing_block,
|
|
308
|
+
"had_existing_file": had_existing_file,
|
|
309
|
+
"preserved_user_content": preserved_user_content,
|
|
310
|
+
}
|
|
@@ -65,11 +65,18 @@ _CREDENTIAL_FALSE_POSITIVES = re.compile(
|
|
|
65
65
|
r"-demo['\"]|"
|
|
66
66
|
# Function-call RHS (reading from parsed JSON, env, getters, slicing strings)
|
|
67
67
|
r"json\.loads|\.read_text\(|\.slice\(|"
|
|
68
|
-
r"
|
|
68
|
+
r"\w+\.get\(|token\s*=\s*_make_token|"
|
|
69
69
|
# RHS that is a parameter reference like token=tokens.get("access_token"...
|
|
70
|
-
r"=\s
|
|
70
|
+
r"=\s*\w+\.get\(|"
|
|
71
71
|
# Dict index dereference: token_data["token"], result["secret"], etc.
|
|
72
|
-
r"_data\[|_result\[
|
|
72
|
+
r"_data\[|_result\[|"
|
|
73
|
+
# Bare `if not <var>:` and similar control-flow lines that mention
|
|
74
|
+
# the credential variable name but contain no value.
|
|
75
|
+
r"if\s+not\s+\w+:|"
|
|
76
|
+
# Python control-flow block-opener: a colon immediately followed by
|
|
77
|
+
# a newline (no quoted value on the same line). Such a colon is an
|
|
78
|
+
# if/while/def/class block-opener, not a key-value separator.
|
|
79
|
+
r":\s*\n)",
|
|
73
80
|
re.IGNORECASE,
|
|
74
81
|
)
|
|
75
82
|
|
|
@@ -192,7 +192,7 @@ def unreleased_feature_detector(
|
|
|
192
192
|
if token in feats:
|
|
193
193
|
continue
|
|
194
194
|
# Don't spam: only flag once per token per text.
|
|
195
|
-
if token not in unknown_specifics and len(token) > 8 and "-" in token:
|
|
195
|
+
if token not in unknown_specifics and len(token) > 8 and "-" in token: # nosec B-secret-detection: `token` here is a Python variable holding one word from the text being grounded, not a credential
|
|
196
196
|
unknown_specifics.append(token)
|
|
197
197
|
|
|
198
198
|
# If we have no whitelist and triggers fired, flag regardless — the
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
"""Inbox drafts registry — LED-1129 Phase 1.
|
|
2
|
+
|
|
3
|
+
Foundation for the autonomous-executor that closes the email→action loop.
|
|
4
|
+
Phase 1 (this module): schema, canonicalization, HMAC binding, SQLite registry.
|
|
5
|
+
NO behavior change — drafts get registered + signed; nobody consumes them yet.
|
|
6
|
+
Phase 2 will add the separate-process executor that reads this registry.
|
|
7
|
+
|
|
8
|
+
See docs/inbox_executor_v1.md for the canonicalization + state-machine spec.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from ai.inbox_drafts.schema import (
|
|
12
|
+
DEFAULT_TTL_SECONDS,
|
|
13
|
+
HMAC_KEY_PATH,
|
|
14
|
+
DraftKind,
|
|
15
|
+
DraftStatus,
|
|
16
|
+
SignedDraft,
|
|
17
|
+
canonicalize,
|
|
18
|
+
content_hash,
|
|
19
|
+
new_draft_id,
|
|
20
|
+
sign_draft,
|
|
21
|
+
verify_draft,
|
|
22
|
+
)
|
|
23
|
+
from ai.inbox_drafts.registry import (
|
|
24
|
+
DEFAULT_DB_PATH,
|
|
25
|
+
DraftRow,
|
|
26
|
+
expire_pending,
|
|
27
|
+
find_draft_by_led_ref,
|
|
28
|
+
get_draft,
|
|
29
|
+
insert_draft,
|
|
30
|
+
list_attempts,
|
|
31
|
+
list_drafts,
|
|
32
|
+
migrate,
|
|
33
|
+
record_attempt,
|
|
34
|
+
transition,
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
__all__ = [
|
|
38
|
+
# schema
|
|
39
|
+
"DEFAULT_TTL_SECONDS",
|
|
40
|
+
"HMAC_KEY_PATH",
|
|
41
|
+
"DraftKind",
|
|
42
|
+
"DraftStatus",
|
|
43
|
+
"SignedDraft",
|
|
44
|
+
"canonicalize",
|
|
45
|
+
"content_hash",
|
|
46
|
+
"new_draft_id",
|
|
47
|
+
"sign_draft",
|
|
48
|
+
"verify_draft",
|
|
49
|
+
# registry
|
|
50
|
+
"DEFAULT_DB_PATH",
|
|
51
|
+
"DraftRow",
|
|
52
|
+
"expire_pending",
|
|
53
|
+
"find_draft_by_led_ref",
|
|
54
|
+
"get_draft",
|
|
55
|
+
"insert_draft",
|
|
56
|
+
"list_attempts",
|
|
57
|
+
"list_drafts",
|
|
58
|
+
"migrate",
|
|
59
|
+
"record_attempt",
|
|
60
|
+
"transition",
|
|
61
|
+
]
|