Skip to content

Commit 40dde1a

Browse files
philcunliffeclaude
andauthored
feat(gascity): codex provider normalizer + provider dispatch (co-276c.4) (#108)
Adds codexNormalize to the gascity normalizer dispatcher, registered alongside claudeNormalize via registerProductionNormalizers. Mirrors bead 2's contract: pure function, no I/O, returns NormalizedRow[] from a raw Codex CLI session frame; everything we don't understand round-trips through raw_frame + attributes. Codex JSONL session logs live at ~/.codex/sessions/YYYY/MM/DD/rollout-*.jsonl with five outer types (session_meta, turn_context, response_item, event_msg, compacted). The normalizer handles all five and maps every nested response_item / event_msg subtype I found in a real session. Key mapping decisions (documented inline + in types.d.ts): - response_item.message.content[] -> N rows of part_type=text, role + phase in attributes (mirrors Claude's multi-block grain). - response_item.function_call -> tool_use; arguments JSON-string parsed into tool_args, call_id -> tool_call_id. - response_item.custom_tool_call -> tool_use; free-form input (apply_patch diff) preserved verbatim in tool_args. - response_item.*_output -> tool_result with tool_result_for linking back to the originating call_id. exec_command outputs are unwrapped to detect exit_code != 0 -> is_error=true. - response_item.reasoning -> thinking; encrypted_content + summary stay in attributes (encrypted by default; readable text drops into content_text when present). - event_msg.token_count.last_token_usage -> input_tokens / output_tokens / cache_read_input_tokens hoisted onto Claude-shaped slots; cached_input_tokens maps to cache_read_input_tokens; reasoning + total counters live under attributes.info. - event_msg.patch_apply_end -> patch_apply_end row with is_error = !success, tool_result_for = call_id; unified diff under attributes.changes. - session_meta hoist -> cwd, git_branch, entrypoint (source), client_version (cli_version); base instructions + git metadata under attributes. - turn_context hoist -> model, cwd; permission_mode left NULL per spec ("don't invent placeholders"); approval_policy / sandbox_policy stay in attributes. Provider_uuid synthesis: Codex frames carry no per-frame uuid, but the schema requires one (writer dedup key). We sha-1 the JSON-serialised frame to produce a stable 40-hex uuid; re-reading the same JSONL line yields the same hash, so a restart + backfill collapses cleanly through the writer's existing dedup. Multi-block frames mirror Claude: N rows share provider_uuid with distinct part_index. The writer's current dedup is keyed only on provider_uuid (parquet_writer.js:200-211), so sibling rows beyond the first are dropped on disk today -- a pre-existing limitation that affects Claude equally and should be fixed in a follow-up bead (dedup should observe `(provider_uuid, part_index)`). Tests: 36 new tests in test/gascity/normalizers/codex.test.js driven by 39 sanitized real frames captured from two real Codex sessions (IDs, paths, encrypted blobs, commit hashes mapped to deterministic tokens; structural shape preserved). Fixtures cover every outer type plus every response_item + event_msg subtype I saw in the wild. Failure-mode tests cover non-object input, unknown frame types, missing timestamps, non-JSON tool arguments. Tool-linkage round-trip test confirms function_call.call_id matches function_call_output .tool_result_for, same for custom_tool_call. Coverage: 87 test files / 1439 tests pass. Typecheck clean. Lint warnings unchanged from baseline (codex.js + codex.test.js carry the same no-extra-parens warnings as claude.js/claude.test.js). End-to-end smoke test (manual, against the real dispatcher): dispatch({provider:"codex", ...function_call_frame}, ctx) -> [{part_type:"tool_use", tool_name:"exec_command", ...}] dispatch({provider:"claude", ...assistant_frame}, ctx) -> [{part_type:"text", model:"claude-opus-4-7", ...}] Confirms mixed-provider routing through the same dispatcher works. Out of scope per bead spec: gemini normalizer (deferred), CLI surface (bead 5), catalog registration (bead 6). Files: - src/gascity/normalizers/codex.js (new) - src/gascity/normalizers/index.js (register codex) - src/gascity/normalizers/types.d.ts (part_type vocab doc) - test/gascity/normalizers/codex.test.js (new) - test/fixtures/gascity/codex/*.jsonl (19 fixture files) Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 480db94 commit 40dde1a

24 files changed

Lines changed: 1571 additions & 6 deletions

src/gascity/normalizers/codex.js

Lines changed: 775 additions & 0 deletions
Large diffs are not rendered by default.

src/gascity/normalizers/index.js

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,12 @@
33
*/
44

55
import { claudeNormalize } from './claude.js'
6+
import { codexNormalize } from './codex.js'
67

78
/**
8-
* Wire production normalizers onto a dispatcher. Bead 2 ships `claude`;
9-
* bead 4 will add `codex`. The unknown-provider passthrough is left to the
10-
* dispatcher's own default bead 3 replaces it with a `raw_frame`-only row.
9+
* Wire production normalizers onto a dispatcher. Beads 2 + 4 ship `claude`
10+
* and `codex`; the unknown-provider passthrough stays the dispatcher's own
11+
* default (bead 3's `raw_frame`-only row).
1112
*
1213
* Exposed as a function (rather than registered at module load) so test
1314
* doubles can build a dispatcher with hand-rolled stubs and bypass real
@@ -18,6 +19,8 @@ import { claudeNormalize } from './claude.js'
1819
*/
1920
export function registerProductionNormalizers(dispatcher) {
2021
dispatcher.register('claude', claudeNormalize)
22+
dispatcher.register('codex', codexNormalize)
2123
}
2224

2325
export { claudeNormalize } from './claude.js'
26+
export { codexNormalize } from './codex.js'

src/gascity/normalizers/types.d.ts

Lines changed: 34 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -44,9 +44,40 @@ export interface NormalizedRow {
4444
message_id: string | null
4545
/** Zero-based block index inside `message.content[]`; 0 for single-row frames. */
4646
part_index: number
47-
/** Block-or-frame type. See README: text / thinking / tool_use / tool_result /
48-
* attachment / last-prompt / permission-mode / file-history-snapshot /
49-
* queue-operation / ai-title / system / user-text / passthrough. */
47+
/**
48+
* Block-or-frame type. The vocabulary is the union of values emitted by
49+
* every registered provider normalizer; new providers extend it without a
50+
* schema bump because the column is plain string.
51+
*
52+
* Claude-shared (cross-provider, queries can target uniformly):
53+
* - `text` — assistant/user content text block
54+
* - `thinking` — assistant reasoning text + signature
55+
* - `tool_use` — outgoing tool call (Claude tool_use / Codex
56+
* function_call / Codex custom_tool_call)
57+
* - `tool_result` — incoming tool response (Claude tool_result / Codex
58+
* function_call_output / Codex custom_tool_call_output)
59+
*
60+
* Claude-only (proxy/native Claude shapes):
61+
* - `attachment` — hook output, skill listing, task reminder, etc.
62+
* - `last-prompt`, `permission-mode`, `file-history-snapshot`,
63+
* `queue-operation`, `ai-title`, `system`
64+
*
65+
* Codex-only (Codex CLI session-log shapes):
66+
* - `session_meta` — one-per-session header (cwd, cli_version, git)
67+
* - `turn_context` — per-turn model/sandbox/approval-policy header
68+
* - `agent_message`, `user_message` — eventized stream copies of the
69+
* `response_item.message` rows, kept for rate/timing queries
70+
* - `task_started`, `task_complete` — per-turn lifecycle (duration_ms,
71+
* time_to_first_token_ms in attributes)
72+
* - `token_count` — periodic usage snapshot with rate_limits
73+
* - `patch_apply_end` — `apply_patch` tool result with unified diff
74+
* - `context_compacted`, `item_completed`, `compacted` — lifecycle
75+
*
76+
* Passthrough (unknown provider):
77+
* - `raw_frame` — bead-3 passthrough one-row-per-frame fallback
78+
* - `unknown` — never emitted by production normalizers (any unmapped
79+
* frame surfaces its native `type` string instead)
80+
*/
5081
part_type: string
5182

5283
// ---------- outer-frame hoist ----------
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
{"timestamp":"2026-05-14T20:53:25.028Z","type":"compacted","payload":{"message":"","replacement_history":[{"type":"message","role":"user","content":[{"type":"input_text","text":"what could we use instead of dolt for beads"}]},{"type":"message","role":"user","content":[{"type":"input_text","text":"what are the commands that use the history, push/pull, conflict\n detection, merge, backup, and multi-agent\n concurrency."}]},{"type":"message","role":"user","content":[{"type":"input_text","text":"is dolt built into this? Or could I use it with anything right now?"}]},{"type":"message","role":"user","content":[{"type":"input_text","text":"I want a lighter version of beads that uses sqlite, can it be done? What commands do we get rid of"}]},{"type":"message","role":"user","content":[{"type":"input_text","text":"would this be easier in this repo or if I made a new repo?"}]},{"type":"message","role":"developer","content":[{"type":"input_text","text":"<permissions instructions>\nFilesystem sandboxing defines which files can be read or written. `sandbox_mode` is `workspace-write`: The sandbox permits reading files, and editing files in `cwd` and `writable_roots`. Editing files in other directories requires approval. Network access is restricted.\n# Escalation Requests\n\nCommands are run outside the sandbox if they are approved by the user, or match an existing rule that allows it to run unrestricted. The command string is split into independent command segments at shell control operators, including but not limited to:\n\n- Pipes: |\n- Logical operators: &&, ||\n- Command separators: ;\n- Subshell boundaries: (...), $(...)\n\nEach resulting segment is evaluated independently for sandbox restrictions and approval requirements.\n\nExample:\n\ngit pull | ...redacted..."},{"type":"input_text","text":"<collaboration_mode># Plan Mode (Conversational)\n\nYou work in 3 phases, and you should *chat your way* to a great plan before finalizing it. A great plan is very detailed—intent- and implementation-wise—so that it can be handed to another engineer or agent to be implemented right away. It must be **decision complete**, where the implementer does not need to make any decisions.\n\n## Mode rules (strict)\n\nYou are in **Plan Mode** until a developer message explicitly ends it.\n\nPlan Mode is not changed by user intent, tone, or imperative language. If a user asks for execution while still in Plan Mode, treat it as a request to **plan the execution**, not perform it.\n\n## Plan Mode vs update_plan tool\n\nPlan Mode is a collaboration mode that can involve requesting user input and eventually issuing a...redacted..."},{"type":"input_text","text":"<skills_instructions>\n## Skills\nA skill is a set of local instructions to follow that is stored in a `SKILL.md` file. Below is the list of skills that can be used. Each entry includes a name, description, and file path so you can open the source for full instructions when using a specific skill.\n### Available skills\n- imagegen: Generate or edit raster images when the task benefits from AI-created bitmap visuals such as photos, illustrations, textures, sprites, mockups, or transparent-background cutouts. Use when Codex should create a brand-new image, transform an existing image, or derive visual variants from references, and the output should be a bitmap asset rather than repo-native code or vector. Do not use when the task is better handled by editing existing SVG/vector/code-native asset...redacted..."},{"type":"input_text","text":"<plugins_instructions>\n## Plugins\nA plugin is a local bundle of skills, MCP servers, and apps. Below is the list of plugins that are enabled and available in this session.\n### Available plugins\n- `Browser`: Browser / browser-use plugin Aliases: @browser, @browser-use, browser-use, Browser, in-app browser. Use Browser, the Codex in-app browser, when the user asks to open, inspect, navigate, test, click, type, or screenshot local web targets such as localhost, 127.0.0.1, ::1, file:// URLs, or the current in-app browser tab. After significant frontend changes to a local app, use Browser to open the relevant local target when it is known or obvious, unless the user asks for another browser tool. For requests like \"open localhost:3000\" or \"open to localhost:4000\", navigate the in-app browser to...redacted..."}]},{"type":"message","role":"user","content":[{"type":"input_text","text":"# AGENTS.md instructions for /sandbox/beads\n\n<INSTRUCTIONS>\n# Agent Instructions\n\n<!-- bd-doctor-divergence: ok -->\n\nSee [AGENT_INSTRUCTIONS.md](AGENT_INSTRUCTIONS.md) for full instructions.\n\nThis file exists for compatibility with tools that look for AGENTS.md.\n\nThe marker above tells `bd doctor` that the intentional divergence between\nthis file and `CLAUDE.md` (different audiences, different reading orders) is\nexpected and should not be flagged.\n\n## Key Sections\n\n- **Issue Tracking** - How to use bd for work management\n- **Development Guidelines** - Code standards and testing\n- **Project Scope** - Read [docs/PROJECT_CHARTER.md](docs/PROJECT_CHARTER.md) before adding new feature surface area\n- **Visual Design System** - Status icons, colors, and semantic styling for CLI output\n- **Contrib...redacted..."},{"type":"input_text","text":"<environment_context>\n <cwd>/sandbox/beads</cwd>\n <shell>zsh</shell>\n <current_date>2026-05-14</current_date>\n <timezone>America/Los_Angeles</timezone>\n</environment_context>"}]},{"type":"message","role":"user","content":[{"type":"input_text","text":"yeah let's do it as a branch, cut all the commands you suggested"}]},{"type":"compaction","encrypted_content":"gAAAAABqBjZEPVQsAOuQXLuwJrLaWNNI33zaTFPB...redacted...EnnwB76oTEDCLM2CY_Lo"}]}}
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
{"timestamp":"2026-05-14T20:58:19.430Z","type":"response_item","payload":{"type":"custom_tool_call","status":"completed","call_id":"call-0005","name":"apply_patch","input":"*** Begin Patch\n*** Update File: /sandbox/beads/internal/storage/storage.go\n@@\n var ErrPrefixMismatch = errors.New(\"prefix mismatch\")\n \n+// ErrUnsupportedCapability is returned by storage backends that intentionally\n+// do not implement optional capabilities such as history, remotes, sync, or\n+// merge.\n+var ErrUnsupportedCapability = errors.New(\"unsupported storage capability\")\n+\n // Storage is the interface satisfied by *dolt.DoltStore.\n*** End Patch\n"}}
2+
{"timestamp":"2026-05-14T20:58:46.355Z","type":"response_item","payload":{"type":"custom_tool_call","status":"completed","call_id":"call-0006","name":"apply_patch","input":"*** Begin Patch\n*** Add File: /sandbox/beads/internal/storage/issueops/dialect_mysql.go\n+//go:build !sqlite_lite\n+\n+package issueops\n+\n+import \"fmt\"\n+\n+func insertIssueSQL(table string) string {\n+\treturn fmt.Sprintf(`\n+\t\tINSERT INTO %s (\n+\t\t\tid, content_hash, title, description, design, acceptance_criteria, notes,\n+\t\t\tstatus, priority, issue_type, assignee, estimated_minutes,\n+\t\t\tcreated_at, created_by, owner, updated_at, started_at, closed_at, external_ref, spec_id,\n+\t\t\tcompaction_level, compacted_at, compacted_at_commit, original_size,\n+\t\t\tsender, ephemeral, no_history, wisp_type, pinned, is_template,\n+\t\t\tmol_type, work_type, source_system, source_repo, close_reason,\n+\t\t\tevent_kind, actor, target, payload,\n+\t\t\tawait_type, await_id, timeout_ns, waiters,\n+\t\t\tdue_at, defer_until, metadata\n+...redacted..."}}
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
{"timestamp":"2026-05-14T20:58:19.504Z","type":"response_item","payload":{"type":"custom_tool_call_output","call_id":"call-0005","output":"{\"output\":\"Success. Updated the following files:\\nM /sandbox/beads/internal/storage/storage.go\\n\",\"metadata\":{\"exit_code\":0,\"duration_seconds\":0.1}}"}}
2+
{"timestamp":"2026-05-14T20:58:46.412Z","type":"response_item","payload":{"type":"custom_tool_call_output","call_id":"call-0006","output":"{\"output\":\"Success. Updated the following files:\\nA /sandbox/beads/internal/storage/issueops/dialect_mysql.go\\n\",\"metadata\":{\"exit_code\":0,\"duration_seconds\":0.1}}"}}
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
{"timestamp":"2026-05-14T20:31:31.060Z","type":"event_msg","payload":{"type":"agent_message","message":"I’ll ground this in the repo’s stated storage boundary first, since the answer depends on what Beads expects from Dolt versus what is incidental.","phase":"commentary","memory_citation":null}}
2+
{"timestamp":"2026-05-14T20:31:37.375Z","type":"event_msg","payload":{"type":"agent_message","message":"The charter makes the key constraint explicit: Beads should talk through a storage driver and avoid leaking storage-engine behavior into core. I’m checking the current storage interface so the alternatives can be judged by how much driver work they imply.","phase":"commentary","memory_citation":null}}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
{"timestamp":"2026-05-14T20:53:25.031Z","type":"event_msg","payload":{"type":"context_compacted"}}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
{"timestamp":"2026-05-14T20:53:45.121Z","type":"event_msg","payload":{"type":"item_completed","thread_id":"thread-0001","turn_id":"turn-0003","item":{"type":"Plan","id":"sess-0002","text":"# SQLite Lite Branch For Beads\n\n## Summary\n\nBuild this on a new branch, tentatively `sqlite-lite`, as a separate lite binary rather than replacing the current Dolt-backed `bd`.\n\nThe default `bd` remains Dolt-backed. The new build target is `bd-lite`, built from the same repo with a `sqlite_lite` build tag. It keeps issue tracking commands that can run locally on SQLite and removes the Dolt/history/sync/merge command surface.\n\n## Key Changes\n\n- Create branch:\n - Run PR preflight for related SQLite/lite work.\n - Create `sqlite-lite` from current `main`.\n\n- Add a SQLite storage backend:\n - New `internal/storage/sqlite` package using `database/sql` and pure-Go SQLite.\n - Store data in `.beads/beads.sqlite3`.\n - Enable `foreign_keys`, WAL mode, and a busy timeout for local concurrent write...redacted..."},"completed_at_ms":1778792025121}}
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
{"timestamp":"2026-05-14T20:58:19.501Z","type":"event_msg","payload":{"type":"patch_apply_end","call_id":"call-0005","turn_id":"turn-0004","stdout":"Success. Updated the following files:\nM /sandbox/beads/internal/storage/storage.go\n","stderr":"","success":true,"changes":{"/sandbox/beads/internal/storage/storage.go":{"type":"update","unified_diff":"@@ -35,2 +35,7 @@\n \n+// ErrUnsupportedCapability is returned by storage backends that intentionally\n+// do not implement optional capabilities such as history, remotes, sync, or\n+// merge.\n+var ErrUnsupportedCapability = errors.New(\"unsupported storage capability\")\n+\n // Storage is the interface satisfied by *dolt.DoltStore.\n","move_path":null}},"status":"completed"}}
2+
{"timestamp":"2026-05-14T20:58:46.411Z","type":"event_msg","payload":{"type":"patch_apply_end","call_id":"call-0006","turn_id":"turn-0004","stdout":"Success. Updated the following files:\nA /sandbox/beads/internal/storage/issueops/dialect_mysql.go\n","stderr":"","success":true,"changes":{"/sandbox/beads/internal/storage/issueops/dialect_mysql.go":{"type":"add","content":"//go:build !sqlite_lite\n\npackage issueops\n\nimport \"fmt\"\n\nfunc insertIssueSQL(table string) string {\n\treturn fmt.Sprintf(`\n\t\tINSERT INTO %s (\n\t\t\tid, content_hash, title, description, design, acceptance_criteria, notes,\n\t\t\tstatus, priority, issue_type, assignee, estimated_minutes,\n\t\t\tcreated_at, created_by, owner, updated_at, started_at, closed_at, external_ref, spec_id,\n\t\t\tcompaction_level, compacted_at, compacted_at_commit, original_size,\n\t\t\tsender, ephemeral, no_history, wisp_type, pinned, is_template,\n\t\t\tmol_type, work_type, source_system, source_repo, close_reason,\n\t\t\tevent_kind, actor, target, payload,\n\t\t\tawait_type, await_id, timeout_ns, waiters,\n\t\t\tdue_at, defer_until, metadata\n\t\t) VALUES (\n\t\t\t?, ?, ?, ?, ?, ?, ?,\n\t\t\t?, ?, ?, ?, ?,\n\t\t\t?, ?, ?, ?, ?, ?, ?, ?,\n\t\t\t?, ?, ?, ?,\n\t\t\t?, ?, ?, ?, ?, ?,\n\t\t\t?, ?, ?, ?, ?,\n\t\t\t?, ?, ?, ?,\n\t\t\t?, ?, ?, ?,\n\t\t\t?, ?, ?\n\t\t)\n\t\tON DUPLICATE KEY UPDATE\n\t\t\tcontent_hash = VALUES(content_hash),\n\t\t\ttitle = VALUES(title),\n\t\t\tdescription = VALUES(description),\n\t\t\tdesign = VALUES(design),\n\t\t\tacceptance_criteria = VALUES(acceptance_criteria),\n\t\t\tnotes = VALUES(notes),\n\t\t\tstatus = VALUES(status),\n\t\t\tpriority = VALUES(priority),\n\t\t\tissue_type = VALUES(issue_type),\n\t\t\tassignee = VALUES(assignee),\n\t\t\testimated_minutes = VALUES(estimated_minutes),\n\t\t\tupdated_at = VALUES(updated_at),\n\t\t\tstarted_at = VALUES(started_at),\n\t\t\tclosed_at = VALUES(closed_at),\n\t\t\texternal_ref = VALUES(external_ref),\n\t\t\tsource_repo = VALUES(source_repo),\n\t\t\tclose_reason = VALUES(close_reason),\n\t\t\tmetadata = VALUES(metadata)\n\t`, table)\n}\n\nfunc insertLabelSQL(table string) string {\n\treturn fmt.Sprintf(`INSERT IGNORE INTO %s (issue_id, label) VALUES (?, ?)`, table)\n}\n\nfunc upsertLabelSQL(table string) string {\n\treturn fmt.Sprintf(`\n\t\tINSERT INTO %s (issue_id, label)\n\t\tVALUES (?, ?)\n\t\tON DUPLICATE KEY UPDATE label = label\n\t`, table)\n}\n\nfunc upsertDependencyNoopSQL(table string) string {\n\treturn fmt.Sprintf(`\n\t\tINSERT INTO %s (issue_id, depends_on_id, type, created_by, created_at)\n\t\tVALUES (?, ?, ?, ?, ?)\n\t\tON DUPLICATE KEY UPDATE type = type\n\t`, table)\n}\n\nfunc upsertChildCounterMaxSQL() string {\n\treturn `\n\t\tINSERT INTO child_counters (parent_id, last_child) VALUES (?, ?)\n\t\tON DUPLICATE KEY UPDATE last_child = GREATEST(last_child, ?)\n\t`\n}\n\nfunc setChildCounterSQL() string {\n\treturn `\n\t\tINSERT INTO child_counters (parent_id, last_child) VALUES (?, ?)\n\t\tON DUPLICATE KEY UPDATE last_child = ?\n\t`\n}\n\nfunc insertDependencyNowSQL(table string) string {\n\treturn fmt.Sprintf(`\n\t\tINSERT INTO %s (issue_id, depends_on_id, type, created_at, created_by, metadata, thread_id)\n\t\tVALUES (?, ?, ?, NOW(), ?, ?, ?)\n\t`, table)\n}\n\nfunc upsertRepoMtimeSQL() string {\n\treturn `\n\t\tINSERT INTO repo_mtimes (repo_path, jsonl_path, mtime_ns, last_checked)\n\t\tVALUES (?, ?, ?, NOW())\n\t\tON DUPLICATE KEY UPDATE\n\t\t\tjsonl_path = VALUES(jsonl_path),\n\t\t\tmtime_ns = VALUES(mtime_ns),\n\t\t\tlast_checked = NOW()\n\t`\n}\n\nfunc upsertFederationPeerSQL() string {\n\treturn `\n\t\tINSERT INTO federation_peers (name, remote_url, username, password_encrypted, sovereignty)\n\t\tVALUES (?, ?, ?, ?, ?)\n\t\tON DUPLICATE KEY UPDATE\n\t\t\tremote_url = VALUES(remote_url),\n\t\t\tusername = VALUES(username),\n\t\t\tpassword_encrypted = VALUES(password_encrypted),\n\t\t\tsovereignty = VALUES(sovereignty),\n\t\t\tupdated_at = CURRENT_TIMESTAMP\n\t`\n}\n\nfunc insertDefaultConfigSQL() string {\n\treturn `\n\t\tINSERT IGNORE INTO config (` + \"`key`\" + `, value) VALUES\n\t\t\t('compaction_enabled', 'false'),\n\t\t\t('compact_tier1_days', '30'),\n\t\t\t('compact_tier1_dep_levels', '2'),\n\t\t\t('compact_tier2_days', '90'),\n\t\t\t('compact_tier2_dep_levels', '5'),\n\t\t\t('compact_tier2_commits', '100'),\n\t\t\t('compact_batch_size', '50'),\n\t\t\t('compact_parallel_workers', '5'),\n\t\t\t('auto_compact_enabled', 'false')\n\t`\n}\n\nfunc jsonPathExistsClause() string {\n\treturn \"JSON_EXTRACT(metadata, ?) IS NOT NULL\"\n}\n\nfunc jsonPathEqualsClause() string {\n\treturn \"JSON_UNQUOTE(JSON_EXTRACT(metadata, ?)) = ?\"\n}\n\nfunc recentIssueSortClause() string {\n\treturn `\n\t\tCASE WHEN created_at >= DATE_SUB(NOW(), INTERVAL 48 HOUR) THEN 0 ELSE 1 END ASC,\n\t\tCASE WHEN created_at >= DATE_SUB(NOW(), INTERVAL 48 HOUR) THEN priority ELSE 999 END ASC,\n\t`\n}\n"}},"status":"completed"}}

0 commit comments

Comments
 (0)