Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
16 commits
Select commit Hold shift + click to select a range
1a0cef4
Mint LLP 0041: central-config-driven client actions implementation de…
philcunliffe Jun 25, 2026
d8f5025
Mint LLP 0043: central-config-driven client actions plan
philcunliffe Jun 26, 2026
457e0c9
T2: confirmation-edge onConfirmed hook in config apply engine
philcunliffe Jun 26, 2026
aa7cb75
T1: action reconciler core + client-actions marker store
philcunliffe Jun 26, 2026
5fa45d0
Validate per-plugin backfill config in @hypaware/claude and @hypaware…
philcunliffe Jun 26, 2026
4c843f4
Merge remote-tracking branch 'origin/task/central-config-client-actio…
philcunliffe Jun 26, 2026
a755d91
Merge remote-tracking branch 'origin/task/central-config-client-actio…
philcunliffe Jun 26, 2026
07729a2
Merge remote-tracking branch 'origin/task/central-config-client-actio…
philcunliffe Jun 26, 2026
7f20c70
Backfill action handler: backfillHandler over selectProviders + per-p…
philcunliffe Jun 26, 2026
335202c
T6: status surface for the client-action reconciler
philcunliffe Jun 26, 2026
3c77bdb
Merge remote-tracking branch 'origin/task/central-config-client-actio…
philcunliffe Jun 26, 2026
2ca1def
Merge remote-tracking branch 'origin/task/central-config-client-actio…
philcunliffe Jun 26, 2026
fea3117
Daemon wiring for the client-action reconciler (T4)
philcunliffe Jun 26, 2026
7361717
Merge remote-tracking branch 'origin/task/central-config-client-actio…
philcunliffe Jun 26, 2026
80172eb
Fix dual-review findings for client-action reconciler (PR #166)
philcunliffe Jun 26, 2026
b2613f7
Round-2 fixes for client-action reconciler (PR #166)
philcunliffe Jun 26, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions hypaware-core/plugins-workspace/claude/hypaware.plugin.json
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,12 @@
"name": "claude-and-otel-local",
"summary": "Capture Claude Code + OTLP locally, export to Parquet under HYP_HOME/exports"
}
],
"config_sections": [
{
"section": "claude",
"summary": "Claude adapter config, including the optional backfill-on-join policy { on_join, window_days }."
}
]
}
}
84 changes: 84 additions & 0 deletions hypaware-core/plugins-workspace/claude/src/config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
// @ts-check

/**
* Config validation for the `@hypaware/claude` plugin's own `config`
* block. v1 validates only the optional `backfill` sub-object that drives
* backfill-on-join — `{ on_join, window_days }`. Every other key (e.g.
* `proxy`) passes through untouched so existing configs keep working;
* there is no top-level `backfill` section and nothing new for core to
* validate.
*
* Pure and dependency-free: it returns a `ValidationResult` so it plugs
* straight into `ctx.configRegistry.registerSection` and is callable from
* tests without spinning up observability.
*
* @import { ValidationError, ValidationResult } from '../../../../collectivus-plugin-kernel-types.d.ts'
*/

/** Manifest `config_sections[].section` name this validator backs. */
export const CLAUDE_CONFIG_SECTION = 'claude'

/**
* Validate the `@hypaware/claude` plugin config slice. Only the optional
* `backfill` policy block is checked; unknown sibling keys are ignored so
* the validator stays additive over the existing config surface.
*
* @ref LLP 0037#per-plugin-config-kernel-generic-reconciler [implements] —
* backfill policy ({ on_join, window_days }) lives in and is validated
* by the source plugin's own config section; the kernel reconciler adds
* no top-level schema.
*
* @param {unknown} value
* @returns {ValidationResult}
*/
export function validateClaudeConfig(value) {
if (value === undefined || value === null) return { ok: true }
if (typeof value !== 'object' || Array.isArray(value)) {
return { ok: false, errors: [{ pointer: '', message: 'claude config must be an object' }] }
}
const raw = /** @type {Record<string, unknown>} */ (value)
const errors = validateBackfillSection(raw.backfill, '/backfill')
if (errors.length > 0) return { ok: false, errors }
return { ok: true }
}

/**
* Validate the optional `backfill` policy block shared by every
* backfill-capable source plugin: `on_join` (whether to import on join,
* boolean) and `window_days` (how far back, positive integer). Both are
* optional; unknown keys are rejected so a typo (`window_day`) surfaces
* instead of being silently ignored. Pure — the caller chooses where the
* returned pointers mount.
*
* @param {unknown} value
* @param {string} pointer JSON-pointer prefix for the `backfill` object
* @returns {ValidationError[]}
*/
export function validateBackfillSection(value, pointer) {
/** @type {ValidationError[]} */
const errors = []
if (value === undefined) return errors
if (value === null || typeof value !== 'object' || Array.isArray(value)) {
errors.push({ pointer, message: 'backfill must be an object' })
return errors
}
const raw = /** @type {Record<string, unknown>} */ (value)
if (raw.on_join !== undefined && typeof raw.on_join !== 'boolean') {
errors.push({ pointer: `${pointer}/on_join`, message: 'backfill.on_join must be a boolean' })
}
if (raw.window_days !== undefined) {
const days = raw.window_days
if (typeof days !== 'number' || !Number.isInteger(days) || days <= 0) {
errors.push({
pointer: `${pointer}/window_days`,
message: 'backfill.window_days must be a positive integer',
})
}
}
for (const key of Object.keys(raw)) {
if (key !== 'on_join' && key !== 'window_days') {
errors.push({ pointer: `${pointer}/${key}`, message: `unknown backfill key '${key}'` })
}
}
return errors
}
25 changes: 25 additions & 0 deletions hypaware-core/plugins-workspace/claude/src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { fileURLToPath } from 'node:url'

import { Attr, getLogger, withSpan } from '../../../../src/core/observability/index.js'
import { defaultConfigPath } from '../../../../src/core/config/schema.js'
import { CLAUDE_CONFIG_SECTION, validateClaudeConfig } from './config.js'
import { attach, defaultSettingsPath, detach } from './settings.js'
import { anthropicUpstreamPreset, createClaudeExchangeProjector } from './projector.js'
import { createClaudeBackfillProvider } from './backfill.js'
Expand All @@ -23,6 +24,19 @@ const CLIENT_NAME = 'claude'
const UPSTREAM_NAME = 'anthropic'
const FALLBACK_BIN_PATH = fileURLToPath(new URL('../../../../bin/hypaware.js', import.meta.url))

/**
* The plugin's `config_sections` validator, surfaced as a side-effect-free
* export so the kernel apply path can validate this plugin's `config` block
* (the `backfill` policy) *before* the plugin is ever activated — e.g. a
* central config that first introduces `@hypaware/claude`. It is the same
* registration `activate()` hands `ctx.configRegistry.registerSection`;
* importing this module never runs `activate()`, so discovery is safe.
*
* @ref LLP 0037#per-plugin-config-kernel-generic-reconciler [implements] — the plugin owns + exposes its own `backfill` validator
* @type {{ section: string, validate: typeof validateClaudeConfig }}
*/
export const configSection = { section: CLAUDE_CONFIG_SECTION, validate: validateClaudeConfig }

/**
* Resolve the canonical session-context state file the Claude hook
* appends to and the projector reads from. Centralised so attach()
Expand Down Expand Up @@ -54,6 +68,17 @@ export function claudeSessionContextFile(ctx) {
* @ref LLP 0016#knows-nothing-about-claude-or-codex [implements] — adapter requires the ai-gateway capability; registers client + upstream preset
*/
export async function activate(ctx) {
// Validate the plugin's own `config` block — currently just the
// optional `backfill` policy ({ on_join, window_days }) that drives
// backfill-on-join. Registered so the kernel runs it via
// `runPerPluginSectionValidators`; no top-level core schema change.
// @ref LLP 0037#per-plugin-config-kernel-generic-reconciler [implements] — the source plugin owns and validates its `backfill` config
ctx.configRegistry.registerSection({
plugin: PLUGIN_NAME,
section: CLAUDE_CONFIG_SECTION,
validate: validateClaudeConfig,
})

/** @type {AiGatewayCapability} */
const gateway = ctx.requireCapability('hypaware.ai-gateway', '^2.0.0')

Expand Down
6 changes: 6 additions & 0 deletions hypaware-core/plugins-workspace/codex/hypaware.plugin.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,12 @@
},
"skills": [
{ "name": "hypaware-query", "clients": ["codex"] }
],
"config_sections": [
{
"section": "codex",
"summary": "Codex adapter config, including the optional backfill-on-join policy { on_join, window_days }."
}
]
}
}
83 changes: 83 additions & 0 deletions hypaware-core/plugins-workspace/codex/src/config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
// @ts-check

/**
* Config validation for the `@hypaware/codex` plugin's own `config`
* block. v1 validates only the optional `backfill` sub-object that drives
* backfill-on-join — `{ on_join, window_days }`. Every other key passes
* through untouched so existing configs keep working; there is no
* top-level `backfill` section and nothing new for core to validate.
*
* Pure and dependency-free: it returns a `ValidationResult` so it plugs
* straight into `ctx.configRegistry.registerSection` and is callable from
* tests without spinning up observability.
*
* @import { ValidationError, ValidationResult } from '../../../../collectivus-plugin-kernel-types.d.ts'
*/

/** Manifest `config_sections[].section` name this validator backs. */
export const CODEX_CONFIG_SECTION = 'codex'

/**
* Validate the `@hypaware/codex` plugin config slice. Only the optional
* `backfill` policy block is checked; unknown sibling keys are ignored so
* the validator stays additive over the existing config surface.
*
* @ref LLP 0037#per-plugin-config-kernel-generic-reconciler [implements] —
* backfill policy ({ on_join, window_days }) lives in and is validated
* by the source plugin's own config section; the kernel reconciler adds
* no top-level schema.
*
* @param {unknown} value
* @returns {ValidationResult}
*/
export function validateCodexConfig(value) {
if (value === undefined || value === null) return { ok: true }
if (typeof value !== 'object' || Array.isArray(value)) {
return { ok: false, errors: [{ pointer: '', message: 'codex config must be an object' }] }
}
const raw = /** @type {Record<string, unknown>} */ (value)
const errors = validateBackfillSection(raw.backfill, '/backfill')
if (errors.length > 0) return { ok: false, errors }
return { ok: true }
}

/**
* Validate the optional `backfill` policy block shared by every
* backfill-capable source plugin: `on_join` (whether to import on join,
* boolean) and `window_days` (how far back, positive integer). Both are
* optional; unknown keys are rejected so a typo (`window_day`) surfaces
* instead of being silently ignored. Pure — the caller chooses where the
* returned pointers mount.
*
* @param {unknown} value
* @param {string} pointer JSON-pointer prefix for the `backfill` object
* @returns {ValidationError[]}
*/
export function validateBackfillSection(value, pointer) {
/** @type {ValidationError[]} */
const errors = []
if (value === undefined) return errors
if (value === null || typeof value !== 'object' || Array.isArray(value)) {
errors.push({ pointer, message: 'backfill must be an object' })
return errors
}
const raw = /** @type {Record<string, unknown>} */ (value)
if (raw.on_join !== undefined && typeof raw.on_join !== 'boolean') {
errors.push({ pointer: `${pointer}/on_join`, message: 'backfill.on_join must be a boolean' })
}
if (raw.window_days !== undefined) {
const days = raw.window_days
if (typeof days !== 'number' || !Number.isInteger(days) || days <= 0) {
errors.push({
pointer: `${pointer}/window_days`,
message: 'backfill.window_days must be a positive integer',
})
}
}
for (const key of Object.keys(raw)) {
if (key !== 'on_join' && key !== 'window_days') {
errors.push({ pointer: `${pointer}/${key}`, message: `unknown backfill key '${key}'` })
}
}
return errors
}
25 changes: 25 additions & 0 deletions hypaware-core/plugins-workspace/codex/src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { fileURLToPath } from 'node:url'

import { Attr, getLogger, withSpan } from '../../../../src/core/observability/index.js'
import { createCodexBackfillProvider } from './backfill.js'
import { CODEX_CONFIG_SECTION, validateCodexConfig } from './config.js'
import { createCodexExchangeProjector } from './exchange-projector.js'
import { attach, defaultConfigPath, detach } from './settings.js'

Expand All @@ -19,6 +20,19 @@ const CLIENT_NAME = 'codex'
const UPSTREAM_NAME = 'openai'
const CHATGPT_UPSTREAM_NAME = 'chatgpt'

/**
* The plugin's `config_sections` validator, surfaced as a side-effect-free
* export so the kernel apply path can validate this plugin's `config` block
* (the `backfill` policy) *before* the plugin is ever activated — e.g. a
* central config that first introduces `@hypaware/codex`. It is the same
* registration `activate()` hands `ctx.configRegistry.registerSection`;
* importing this module never runs `activate()`, so discovery is safe.
*
* @ref LLP 0037#per-plugin-config-kernel-generic-reconciler [implements] — the plugin owns + exposes its own `backfill` validator
* @type {{ section: string, validate: typeof validateCodexConfig }}
*/
export const configSection = { section: CODEX_CONFIG_SECTION, validate: validateCodexConfig }

/**
* Activate the `@hypaware/codex` adapter plugin.
*
Expand All @@ -35,6 +49,17 @@ const CHATGPT_UPSTREAM_NAME = 'chatgpt'
* @ref LLP 0016#knows-nothing-about-claude-or-codex [implements] — adapter requires the ai-gateway capability; registers client + upstream presets
*/
export async function activate(ctx) {
// Validate the plugin's own `config` block — currently just the
// optional `backfill` policy ({ on_join, window_days }) that drives
// backfill-on-join. Registered so the kernel runs it via
// `runPerPluginSectionValidators`; no top-level core schema change.
// @ref LLP 0037#per-plugin-config-kernel-generic-reconciler [implements] — the source plugin owns and validates its `backfill` config
ctx.configRegistry.registerSection({
plugin: PLUGIN_NAME,
section: CODEX_CONFIG_SECTION,
validate: validateCodexConfig,
})

/** @type {AiGatewayCapability} */
const gateway = ctx.requireCapability('hypaware.ai-gateway', '^2.0.0')

Expand Down
Loading