mirror of
https://github.com/pewdiepie-archdaemon/odysseus.git
synced 2026-10-27 00:02:21 +01:00
Merge current dev and refresh PR #6741 from the 7011 integration
This commit is contained in:
@@ -0,0 +1,201 @@
|
||||
# Editor Agent API
|
||||
|
||||
How an agent (LLM or script) drives the PhotoCraft editor. There are three layers:
|
||||
|
||||
1. **Control API** — `window.geEditorControl`, a stable in-page command surface.
|
||||
2. **NL planner** — `window.geEditorAI`, turns a prompt into control-API steps (LLM or basic mode).
|
||||
3. **Bridge** — server-mediated SSE channel so a *server-side* agent can drive an **open** editor.
|
||||
|
||||
All three are owner-scoped by the app session; nothing is exposed to unauthenticated users.
|
||||
|
||||
---
|
||||
|
||||
## 1. Control API (`window.geEditorControl`)
|
||||
|
||||
Installed when the editor opens. Version `EDITOR_CONTROL_VERSION`.
|
||||
|
||||
```js
|
||||
await window.geEditorControl.invoke(op, params) // { ok, op, result } | { ok:false, op, error }
|
||||
await window.geEditorControl.run([{op, params}]) // sequential, stops on first failure
|
||||
window.geEditorControl.list() // [{op, description}]
|
||||
```
|
||||
|
||||
`invoke` is async. Every op that mutates the document pushes **one** undo entry via
|
||||
`_saveState`, so a single op is one undo step.
|
||||
|
||||
### Op catalog
|
||||
|
||||
| Op | Params | Notes |
|
||||
| --- | --- | --- |
|
||||
| `document.info` | – | size, layers, mode |
|
||||
| `document.importImage` | `{dataUrl,name,raster}` | load a raster image as a new layer |
|
||||
| `layers.list` | – | top-most first |
|
||||
| `layers.select` | `{id\|index\|name}` | index is top-most-first |
|
||||
| `layers.addRaster` | – | new empty pixel layer |
|
||||
| `layers.addAdjustment` | `{type,params}` | adjustment layer |
|
||||
| `layers.duplicate` / `layers.delete` | – | on the active/selected layer(s) |
|
||||
| `layers.setOpacity` | `{id?,opacity 0-1}` | |
|
||||
| `layers.setBlend` | `{id?,blendMode\|mode}` | |
|
||||
| `layers.group` / `layers.ungroup` | – | |
|
||||
| `selection.selectAll` / `deselect` / `invert` | – | |
|
||||
| `selection.rectangle` / `selection.ellipse` | `{x,y,w,h}` | document space |
|
||||
| `selection.subject` | `{label}` | SAM text grounding (falls back to a centred rect) |
|
||||
| `selection.face` | – | SAM "face" (falls back to an upper-centre ellipse) |
|
||||
| `selection.sky` | – | SAM "sky" (falls back to a top-band colour range) |
|
||||
| `selection.background` | `{fuzziness}` | border colour union |
|
||||
| `selection.samText` | `{text}` | SAM text grounding only (throws without SAM) |
|
||||
| `selection.colorRange` | `{color,fuzziness}` | |
|
||||
| `selection.hasMask` | – | `{active, space}` |
|
||||
| `adjustments.apply` | `{type,params}` | adjustment layer; camelCase types accepted |
|
||||
| `adjustments.applyMasked` | `{brightness,contrast,saturation,warmth,invert}` | pixel pass, masked if a selection exists else whole layer |
|
||||
| `filters.apply` | `{id}` | opens the slider dialog and applies |
|
||||
| `filters.applyMasked` | `{id,radius,amount,invert}` | `id`: blur-gaussian, sharpen, unsharp-mask |
|
||||
| `transform.start` / `transform.apply` / `transform.cancel` | – | |
|
||||
| `text.add` | `{text,x,y,color,size,font,align}` | |
|
||||
| `history.undo` / `history.redo` | – | |
|
||||
| `history.mark` | – | returns current undo depth (for grouping) |
|
||||
| `history.collapse` | `{mark,label}` | collapses entries above `mark` into one |
|
||||
| `export.png` | – | `{dataUrl}` of the flattened document |
|
||||
| `export.gallery` | – | saves a copy back to the gallery |
|
||||
| `screenshot` | – | `{dataUrl,width,height}` of the composited canvas |
|
||||
|
||||
Param conventions the planner must respect:
|
||||
|
||||
- `adjustments.applyMasked`: `brightness`/`contrast`/`warmth` are **-100..100** (0 neutral);
|
||||
`saturation` is a **multiplier** (1 neutral; values >3 are treated as percentages).
|
||||
- `adjustments.apply` `type` aliases are normalised (`blackAndWhite` -> `black-white`).
|
||||
- `color-balance` params are `{shadows:{r,g,b}, midtones:{r,g,b}, highlights:{r,g,b}}`;
|
||||
a bare number is coerced to `{r:n,g:n,b:n}`.
|
||||
- A masked op with **no** active selection degrades to a whole-layer edit (does not throw).
|
||||
|
||||
---
|
||||
|
||||
## 2. NL planner (`window.geEditorAI`)
|
||||
|
||||
```js
|
||||
const res = await window.geEditorAI.execute('blur the background behind the person', { onStep });
|
||||
// exec: window.geEditorAI.execute('blur the background behind the person', { onStep });
|
||||
// res = { ok, prompt, reply, planner, basicMode, steps, trace, failed }
|
||||
```
|
||||
|
||||
Flow (`static/js/editor/nl-edit.js`):
|
||||
|
||||
1. Read document state + a **before** screenshot.
|
||||
2. Ask the **server planner** (`POST /api/editor/plan`) with the prompt, state and
|
||||
screenshot. The client passes its strong guidance via `state.planner_guidance`.
|
||||
If the route is unavailable it falls back to `localPlan` (regex — labelled
|
||||
**"basic mode"** in the UI).
|
||||
3. `sanitizeSteps()`: drop no-op/export steps and, if a masked op has no preceding
|
||||
`selection.*`, insert the region implied by the prompt (face/sky/background/subject).
|
||||
4. `history.mark`, then run each step, screenshotting after each one.
|
||||
5. **Self-check** (server plans only, max 3 rounds): send the after screenshot back in
|
||||
`mode:"check"` with the original intent. If not satisfied, **revert** the previous
|
||||
attempt (undo its history entries) and apply the model's **full corrected** plan.
|
||||
6. `history.collapse` — the whole request becomes a single undoable step.
|
||||
|
||||
UI: the Quick Edit box routes multi-clause / "and ..." prompts to this executor and
|
||||
streams `Step i/n: <op>` into its status line; a `(basic mode)` suffix marks the offline planner.
|
||||
|
||||
### Server route
|
||||
|
||||
`POST /api/editor/plan` (HARNESS, `routes/editor_agent_routes.py`)
|
||||
|
||||
```
|
||||
{ prompt, state, mode?: "plan"|"check", screenshot?: "data:image/..." }
|
||||
-> { reply, planner, steps: [{op, params}], done? }
|
||||
```
|
||||
|
||||
Owner-scoped, authenticated. Uses the configured default/utility model with vision.
|
||||
If the model call is unavailable it returns a deterministic `planner:"fallback"` plan.
|
||||
|
||||
---
|
||||
|
||||
## 3. Bridge (server agent drives an open editor)
|
||||
|
||||
`static/js/editor/editor-bridge.js` subscribes the open editor to the server queue.
|
||||
Enabled automatically when the editor opens (`window.__geEditorBridge`).
|
||||
|
||||
- Subscribe: `GET /api/editor/bridge/events?session=<id>` — SSE, `data: {id,op,params}` (keepalive every 15s).
|
||||
- The page runs the op via the control API and replies:
|
||||
`POST /api/editor/bridge/result {id,ok,result,error,screenshot,ms}`.
|
||||
- Enqueue a command (server side): `POST /api/editor/bridge/command {session, op|steps, wait, timeout}`.
|
||||
With `wait:true` the call blocks and returns the editor's result.
|
||||
|
||||
Round-trip example (authenticated; `<session>` = `window.__geEditorBridge.session`):
|
||||
|
||||
```bash
|
||||
curl -s -X POST http://127.0.0.1:7011/api/editor/bridge/command \
|
||||
-H 'Content-Type: application/json' -b "odysseus_session=$TOKEN" \
|
||||
-d '{"session":"default","op":"layers.addRaster","wait":true,"timeout":8000}'
|
||||
# -> {"id":"...","ok":true,"result":{...},"screenshot":"data:image/png;base64,..."}
|
||||
```
|
||||
|
||||
Session scoping is `owner:session`, so an agent can only drive editors owned by the
|
||||
same user. The bridge is a **browser-only** flow: it requires an editor page to be open.
|
||||
|
||||
---
|
||||
|
||||
## 4. SAM dependency
|
||||
|
||||
`selection.subject` / `selection.face` / `selection.sky` / `selection.samText` use the
|
||||
server SAM mask tool (`POST /api/image/mask`). When the SAM tools are **not installed**
|
||||
the editor raises *"Object mask tools are not installed"* and the control op falls back
|
||||
to a **coarse approximation** (centred rect / upper-centre ellipse / top-band colour range).
|
||||
On a plain background the approximation is acceptable; on real photos it leaks, producing
|
||||
visible rectangular/elliptical artifacts. Install the Cookbook SAM mask dependencies for
|
||||
true subject/face/sky segmentation.
|
||||
|
||||
`selection.colorRange` and `selection.background` never need SAM (pure colour matching),
|
||||
but they are also coarse on busy scenes.
|
||||
|
||||
---
|
||||
|
||||
## 5. Two ways for an agent to use this — pick one
|
||||
|
||||
### Option A — built-in tool (in-process)
|
||||
|
||||
Register an editor-control tool inside the app (same process as the model) that calls the
|
||||
bridge/control API directly. Uses the app's normal login; nothing new is exposed. This is
|
||||
the rejected stdio-MCP design's replacement: a server-side tool that talks to the bridge
|
||||
needs either an internal token (auth-bypass) or to run in-process. **Decision pending (user).**
|
||||
|
||||
### Option B — browser-only flow (works today)
|
||||
|
||||
No new server tool. An external agent drives an open editor purely through the bridge
|
||||
endpoints using the logged-in owner's session. Restricted to the editor the user has open.
|
||||
This is the flow demonstrated above and requires **no** auth-bypass.
|
||||
|
||||
A stdio `photo_editor` MCP server was prototyped and **rejected** because the built-in MCP
|
||||
subprocess has no internal token; wiring it would require expanding an auth bypass.
|
||||
The two viable options above are left for the user to choose.
|
||||
|
||||
---
|
||||
|
||||
## 6. Known limitations / honest grading
|
||||
|
||||
Live run against real 7011 with the server LLM planner (7 prompts): all 7 executed
|
||||
(`planner:"llm"`). Graded by eye:
|
||||
|
||||
- PASS: black & white portrait; warm cinematic; sharper + more vivid; text title.
|
||||
- FAIL (region-masked, SAM-bound): "dramatic sky" (global over-saturation instead of a
|
||||
dramatic, masked sky), "blur the background" (coarse mask leaks; visible rectangle),
|
||||
"brighten the face" (coarse ellipse/rect artifact).
|
||||
|
||||
Whole-image edits are reliable; subject/face/sky region edits need SAM. The self-check
|
||||
loop works (revert-and-replan) but the region cases spend all 3 iterations.
|
||||
|
||||
## 7. Theming (control kit)
|
||||
|
||||
The editor's interactive controls (ranges, number fields, selects, checkboxes) share a
|
||||
single themed kit: `static/js/editor/control-kit.js` adds `.ge-ctl-*` classes scoped to
|
||||
the editor roots (`#gallery-editor-container`, `.ge-adj-popup`, `.ge-filter-overlay`,
|
||||
`.ge-canvas-size-overlay`, `.ge-float-window`, `.ge-transform-popup`), and
|
||||
`static/css/13-image-editor.css` (section "Editor control kit") styles them from the app
|
||||
design tokens (`static/css/00-tokens.css`) to match the app sliders/selects. It keeps
|
||||
range fills in sync and attaches a **scrub-on-label** affordance to numeric fields
|
||||
(drag the field's label to change the value; arrow keys work, Shift = x10).
|
||||
|
||||
**The PhotoCraft editor is dark-only.** `:root.light` flips the global tokens, but
|
||||
`13-image-editor.css` has no `.light` handling and the editor relies on dark surfaces,
|
||||
so light mode renders a garbled mix. Unlike the rest of Odysseus, the editor follows
|
||||
Photoshop and is not offered in a light theme.
|
||||
@@ -0,0 +1,255 @@
|
||||
# Quick Edit Coverage Audit
|
||||
|
||||
Audit of how much of the PhotoCraft editor is reachable through **Quick Edit**
|
||||
(the natural-language editor agent). Built against build **0.20.44** (HARNESS
|
||||
bridge fan-out fix `4dd11826`) and client commit `e78b5a96`.
|
||||
|
||||
Quick Edit executes in two stages:
|
||||
|
||||
1. **Plan** — `static/js/editor/editor-agent.js` posts `{prompt, state,
|
||||
screenshot}` to `/api/editor/agent`. HARNESS `routes/editor_agent_routes.py`
|
||||
plans with the shared style guide (`src/editor_style_guide.py`) plus an LLM,
|
||||
validates every step against its own `OP_CATALOG`, then executes.
|
||||
2. **Execute** — only `ai_edit_image` / `generate_image` run server-side; every
|
||||
other op is delivered to the page over the editor bridge (SSE) and run by the
|
||||
client control surface (`window.geEditorControl.invoke`, catalog in
|
||||
`static/js/galleryEditor.js`). A local fallback planner lives in
|
||||
`static/js/editor/nl-edit.js`.
|
||||
|
||||
There are **three** catalogs that must agree:
|
||||
|
||||
| Catalog | File | Owner |
|
||||
|---|---|---|
|
||||
| Client control ops | `static/js/galleryEditor.js` | PHOTOSHOP |
|
||||
| Server `OP_CATALOG` | `routes/editor_agent_routes.py` | HARNESS |
|
||||
| Style guide / planner | `src/editor_style_guide.py` + `nl-edit.js` | HARNESS + PHOTOSHOP |
|
||||
|
||||
Legend: **C** = in client catalog · **S** = in server `OP_CATALOG` (incl.
|
||||
`NEW_OP_NAMES`) · **P** = the planner/style guide emits it for some phrasing.
|
||||
A capability is **usable from Quick Edit** only when the planner can emit it
|
||||
(**P**) and the op exists on the side that runs it.
|
||||
|
||||
---
|
||||
|
||||
## 1. Menu bar (12 menus)
|
||||
|
||||
| Capability | Trigger phrasing | Op | C | S | P | Status |
|
||||
|---|---|---|---|---|---|---|
|
||||
| File → Import | "open this image" | `document.importImage` | ✔ | ✔ | partial | OK |
|
||||
| File → Load project | "open my project" | `document.openPsd` | ✔ | ✘ | ✘ | **Server gap** |
|
||||
| File → Save / Save copy / Export PSD | "save as psd" | `document.savePsd` | ✔ | ✔ | ✔ | OK |
|
||||
| File → Export | "export png" | `export.png` | ✔ | ✔ | ✔ | OK |
|
||||
| File → Save project | — | (project store) | n/a | n/a | ✘ | Out of scope (not an edit op) |
|
||||
| Edit → Undo/Redo/History | "undo" | `history.undo` / `redo` | ✔ | ✔ | ✔ | OK |
|
||||
| Edit → Free Transform | "free transform" | `transform.start` | ✔ | ✔ | ✔ | OK |
|
||||
| Edit → Transform → Rotate 90/180 | "rotate this layer 90" | `layer.rotate` | ✔ | ✔ | ✔ | OK (whole image vs layer routed by the fast path) |
|
||||
| Edit → Transform → Flip H/V | "flip this layer" | `layer.flip` | ✔ | ✔ | ✔ | OK (whole image vs layer routed by the fast path) |
|
||||
| Image → Fill | "fill the whole image with white" | — | ✘ | ✘ | ✘ | **No op** |
|
||||
| Image → Canvas Size | "make the canvas wider" | `document.resizeCanvas` | ✔ | ✔ | partial | OK |
|
||||
| Image → Image Size | "resize the image to 1200px" | `document.resizeCanvas` | ✔ | ✔ | partial | OK |
|
||||
| Image → Rotate 90/180 (whole image) | "rotate the whole image 90" | `document.rotate`/all-layers | ✘ | ✘ | ✘ | **No control op** |
|
||||
| Image → Flip H/V (whole image) | "flip the whole canvas" | `document.flip`/all-layers | ✘ | ✘ | ✘ | **No control op** |
|
||||
| Image → Mode RGB/Grayscale | "make it black and white" | `adjustments.apply` black-white | ✔ | ✔ | ✔ | OK (mode via adjustment) |
|
||||
| Image → Adjustments → Invert | "invert the colours" | `adjustments.apply` invert | ✔ | ✔ | ✔ | OK (fast path colour-inverts the active layer; "invert the selection" stays Select ▸ Inverse) |
|
||||
| Image → Mode CMYK/Lab, Bits 16/32 | — | — | ✘ | ✘ | ✘ | Not implemented (disabled in UI) |
|
||||
| Layer → New | "add a layer" | `layers.addRaster` | ✔ | ✔ | partial | OK |
|
||||
| Layer → Duplicate | "duplicate the layer" | `layers.duplicate` | ✔ | ✔ | partial | OK |
|
||||
| Layer → Group | "group the layers" | `layers.group` | ✔ | ✔ | ✘ | **Server OK, planner gap** |
|
||||
| Layer → Rasterize | "rasterize the text" | `layers.rasterize` | ✔ | ✔ | ✔ | OK |
|
||||
| Layer → Delete | "delete the layer" | `layers.delete` | ✔ | ✔ | partial | OK |
|
||||
| Layer → Merge Down | "merge down" | `layers.mergeDown` | ✔ | ✘ | ✘ | **Server gap** |
|
||||
| Layer → Merge All / Flatten | "flatten the image" | `layers.flatten` | ✔ | ✘ | ✘ | **Server gap** |
|
||||
| Layer → Select All | "select all layers" | (multi-select) | ✔ | ✔ | ✔ | OK (fast path selects every layer) |
|
||||
| Layer → Combine (unite/subtract/…) | "combine the shapes" | `_combineSelectedShapes` | ✔ | ✔ | ✔ | OK (fast path selects the shape layers and combines) |
|
||||
| Layer → Fill | "fill the layer with blue" | `selection.fill` / flood | partial | ✘ | ✘ | **Gap** |
|
||||
| Layer → Layer Mask (reveal/hide/apply/invert/feather) | "add a layer mask" | `layers.addMaskFromSelection` | ✔ | ✔ | ✔ | OK |
|
||||
| Layer → Opacity | "set the layer opacity to 50%" | `layers.setOpacity` | ✔ | ✔ | partial | OK (fast path sets the active layer's opacity) |
|
||||
| Layer → Blend Mode | "set the blend mode to multiply" | `layers.setBlend` | ✔ | ✔ | partial | OK (fast path sets the active layer's blend mode) |
|
||||
| Type → Add | "add a title" | `text.add` | ✔ | ✔ | ✔ | OK |
|
||||
| Type → Character | "make the text bigger" | `text.edit` | ✔ | ✘ | ✘ | **Server gap + planner gap** |
|
||||
| Type → Paragraph | "centre the text" | `text.layout` | ✔ | ✔ | partial | OK |
|
||||
| Type → Warp | "warp the text" | `_rerenderTextLayer` (warp) | ✔ | ✔ | ✔ | OK |
|
||||
| Select → All/Deselect/Invert | "invert the selection" | `selection.*` | ✔ | ✔ | ✔ | OK |
|
||||
| Select → Reselect | "reselect" | `_reselectSelection` | ✔ | ✔ | ✔ | OK |
|
||||
| Select → Transform | "transform the selection" | `transform.start` | ✔ | ✔ | partial | OK |
|
||||
| Select → Refine | "refine the edge" | `selection.feather/expand` | ✔ | ✘ | ✘ | **Server gap** |
|
||||
| Select → Color Range | "select the blue sky" | `selection.colorRange` | ✔ | ✔ | partial | OK |
|
||||
| Select → Select and Mask | "select the subject" | `selection.subject` | ✔ | ✔ | ✔ | OK |
|
||||
| Select → Save | "save the selection" | `_saveNamedSelection` | ✔ | ✔ | ✔ | OK |
|
||||
| Select → Load | "load the selection as sky" | `_loadNamedSelection` | ✔ | ✔ | ✔ | OK |
|
||||
| Filter → every filter (30+) | "pixelate the image" | `filters.apply` | ✔ | ✔ | ✔ | OK (fast path covers pixelate/emboss/twirl/pinch/spherize/ripple/solarize/find-edges/crystallize/noise/despeckle/unsharp + blur/sharpen/vignette) |
|
||||
| Filter → Blur → Motion / Zoom | "motion blur" / "zoom blur" | `filters.apply` (blur-motion / blur-zoom) | ✔ | ✔ | ✔ | OK (fast path maps motion blur and zoom blur to their Filter-menu variants) |
|
||||
| Filter → Layer effects (drop shadow, stroke, colour overlay, outer glow) | "add a drop shadow" | `layers.addStyle` | ✔ | ✔ | ✔ | OK (fast path adds drop shadow / stroke / outer glow / colour & gradient overlay; colour parsed from the prompt) |
|
||||
| View → Rulers/Grid/Snap/Clear guides | "show the grid" | `#ge-view-menu` | ✔ | ✔ | ✔ | OK |
|
||||
| Window → panels | — | n/a | n/a | n/a | ✘ | UI-only |
|
||||
| 3D / Plugins | — | — | ✘ | ✘ | ✘ | Not implemented (disabled in UI) |
|
||||
|
||||
## 2. Tools
|
||||
|
||||
Toolbar tools are interactive, not bridge ops, so coverage is about whether the
|
||||
**effect** of a tool is reachable by prompt.
|
||||
|
||||
| Tool | NL-reachable? | Notes |
|
||||
|---|---|---|
|
||||
| Move / Transform | `transform.*` | OK (transform.start/apply/cancel) |
|
||||
| Hand / Zoom | — | View only |
|
||||
| Crop | `document.crop` + format guard | OK |
|
||||
| Text / Shape | `text.add`, `text.layout` | Shape has no add-shape op — **gap** |
|
||||
| Brush / Eraser / Dodge / Burn / Smudge / Clone / Heal / Sharpen | — | Interactive painting; no NL op — **gap** (out of scope for planner) |
|
||||
| Gradient | — | **No op** |
|
||||
| **Paint Bucket** | `fill.flood` | **Server gap + planner gap** (op exists client-side only) |
|
||||
| Eyedropper | — | UI only |
|
||||
| Marquee / Lasso / Wand / Quick Select / Object Select / Pen / Path | `selection.rectangle/ellipse/polygon/subject/…` | OK for region/reference selects; lasso/pen paths have `selection.polygon` only |
|
||||
| SAM / Inpaint / Rembg | `selection.subject`, `ai.removeObject`, `ai.removeBackground` | OK |
|
||||
| **Pixel brush + grid** | — | UI state only; no op — **gap** (acceptable: it is a brush mode, not an edit op) |
|
||||
|
||||
## 3. Right-click context menu
|
||||
|
||||
| Item | Backing op | Reachable by prompt? |
|
||||
|---|---|---|
|
||||
| Brush size / preset / Pixel Brush | `state` UI | No (interactive) |
|
||||
| Select All / Deselect / Inverse / Feather / Expand / Contract / Border / Smooth | `selection.*` | Yes |
|
||||
| Transform Selection / Refine Edge | `transform.start`, `selection.feather` | Partial |
|
||||
| Apply / Cancel Crop | `document.crop` | Yes |
|
||||
| Commit / Cancel / Free Transform | `transform.apply/cancel/start` | Yes |
|
||||
| Add Text Layer / Rasterize Text | `text.add`, `layers.rasterize` | Yes (rasterize OK) |
|
||||
| Copy Color Hex / Swap Colors | `state.color` | OK (fast path swaps / resets the foreground & background swatches) |
|
||||
| Fill Selection w/ Foreground | `selection.fill` | OK (fast path fills the selection/canvas with the foreground or background colour) |
|
||||
| Cut / Copy / Paste | `selection.copy/paste` | **Gap** (no planner intent) |
|
||||
| Rotate 90/180, Flip H/V | `layer.rotate/flip` | **Server+planner gap** (same as Edit menu) |
|
||||
| Undo / Redo | `history.*` | Yes |
|
||||
|
||||
## 4. Adjustments
|
||||
|
||||
`adjustments.apply` / `adjustments.applyMasked` cover all adjustment types
|
||||
(brightness-contrast, white-balance, shadows-highlights, vibrance, clarity,
|
||||
vignette, black-white, …). The style guide emits several. **Status: OK**
|
||||
(planner covers the common ones; the long tail is reachable only via explicit
|
||||
wording).
|
||||
|
||||
## 5. Filters
|
||||
|
||||
`filters.apply` / `filters.applyMasked` run every filter the UI lists. The
|
||||
planner only emits **sharpen**, **blur-gaussian**, and **vignette** via intents.
|
||||
**Gap (planner):** no intent for noise, pixelate, stylize, distort, render,
|
||||
unsharp, sharpen-edges, colour-overlay/drop-shadow/stroke.
|
||||
**Gap (HARNESS):** `filters.apply`/`applyMasked` are in the server catalog, so
|
||||
the fix is style-guide intents, not catalog.
|
||||
|
||||
## 6. Layer styles
|
||||
|
||||
`layers.addStyle {type, params}` exists in both catalogs. **Planner never emits
|
||||
it**, so "add a drop shadow to the title", "add a stroke", "colour overlay" fail.
|
||||
**Gap (planner).**
|
||||
|
||||
## 7. Selection ops
|
||||
|
||||
`selection.rectangle/ellipse/polygon/fill/delete/copy/paste/feather/expand/
|
||||
contract/selectAll/deselect/invert/colorRange/subject/samText/face/sky/
|
||||
background/hasMask/maskBounds/exportMask/regionLuminance`. **Status: mostly OK.**
|
||||
Missing: reselect, save-selection.
|
||||
|
||||
## 8. Document ops
|
||||
|
||||
| Capability | Op | C | S | P |
|
||||
|---|---|---|---|---|
|
||||
| Crop | `document.crop` | ✔ | ✔ | ✔ |
|
||||
| Resize canvas / image | `document.resizeCanvas` | ✔ | ✔ | partial |
|
||||
| **Extend canvas** | `document.extend` | ✔ | ✘ | ✘ |
|
||||
| Save PSD | `document.savePsd` | ✔ | ✔ | ✔ |
|
||||
| Export layers | `document.exportLayers` | ✔ | ✘ | ✘ |
|
||||
| Duplicate / Create / Switch / List | `document.*` | ✔ | ✘ | partial |
|
||||
|
||||
## 9. AI edit & generate modes
|
||||
|
||||
| Mode | Op | Notes |
|
||||
|---|---|---|
|
||||
| Generative fill | `ai.generativeFill` | OK |
|
||||
| Remove object | `ai.removeObject` | OK |
|
||||
| Harmonize | `ai.harmonize` | OK |
|
||||
| Upscale | `ai.generativeUpscale` | OK |
|
||||
| Generate image | `ai.generateImage` / server `generate_image` | OK |
|
||||
| Expand | `ai.generativeExpand` | OK (content-bounds prefill, SW v760) |
|
||||
| Remove background | `ai.removeBackground` | OK |
|
||||
| Instruction edit / scene replace | server `ai_edit_image` | OK (HARNESS) |
|
||||
|
||||
---
|
||||
|
||||
## 10. Gap list
|
||||
|
||||
### Client-side (fix in this repo — PHOTOSHOP-owned) — **ALL FIXED**
|
||||
1. ✅ **`fill.flood` not in the NL path** — added to `nl-edit.js OP_CATALOG` +
|
||||
`localPlan` fill intent ("fill … with <colour>"; falls back to `selection.fill`
|
||||
when a region is selected).
|
||||
2. ✅ **`layer.flip` / `layer.rotate` not in the NL path** — added to
|
||||
`nl-edit.js OP_CATALOG` + "this layer" intent (whole-image phrasing routes to
|
||||
`document.flip`/`document.rotate`).
|
||||
3. ✅ **`text.edit` not in the NL path** — added catalog entry + intent for font
|
||||
size/colour ("make the text bigger/smaller", "make it red").
|
||||
4. ✅ **`document.extend` not in the NL path** — added catalog entry + intent for
|
||||
"extend the canvas to 16:9" (derives target size from the document).
|
||||
5. ✅ **`layers.align`** — added planner intent ("align … to the centre/left/…").
|
||||
6. ✅ **`layers.addStyle`** — added planner trigger ("drop shadow", "stroke",
|
||||
"colour overlay").
|
||||
7. ✅ **Whole-image rotate/flip has no control op** — added `document.rotate` /
|
||||
`document.flip` control ops (`galleryEditor.js`) and catalog/intent entries.
|
||||
8. ✅ **`selection.copy/paste` intents** — added "copy the selection"/"paste".
|
||||
|
||||
### Server-side (file for HARNESS in the passover)
|
||||
1. `routes/editor_agent_routes.py OP_CATALOG` is missing ops the client runs:
|
||||
`layer.flip`, `layer.rotate`, `text.edit`, `fill.flood`, `layers.align`,
|
||||
`layers.rasterize`, `layers.mergeDown`, `layers.flatten`, `document.extend`,
|
||||
`document.exportLayers`, `document.*` (list/duplicate/create/switch/openPsd),
|
||||
`mask.*`, `selection.*` (polygon/feather/expand/contract/copy/paste/hasMask/
|
||||
face), `transform.*` are partly present.
|
||||
2. `src/editor_style_guide.py` has no intents for: layer transform
|
||||
(rotate/flip active layer), whole-document rotate/flip, text editing, align,
|
||||
layer styles (drop shadow/stroke/overlay), flood fill, copy/paste, noise/
|
||||
pixelate/stylize/distort/render filters.
|
||||
|
||||
## 11. Coverage summary
|
||||
|
||||
Counting distinct user-facing capabilities in the toolbar + 12 menus + right-click
|
||||
(~150 including the filter/effect long tail):
|
||||
|
||||
- **Have a client op:** ~85%
|
||||
- **Client op also in server OP_CATALOG:** ~75%
|
||||
- **Planner can emit for natural phrasing:** ~72% (was ~55%; client-side
|
||||
planner intents for fill, layer/whole-image transform, text edit, align,
|
||||
layer styles, copy/paste and extend are now implemented)
|
||||
- **Usable from Quick Edit end-to-end today:** **45/45 prompts (100%)** measured
|
||||
live on build **0.20.45** with `tests/e2e/photo-editor/quickedit-coverage.js`
|
||||
(`/tmp/opencode/qe-coverage45/quickedit-coverage.json`). HARNESS build 0.20.45
|
||||
(`4a6932a2`) added the 12 missing structural ops to the server `OP_CATALOG` +
|
||||
style guide v5, so every audited prompt now plans to the exact op.
|
||||
|
||||
The gap was dominated by the **planner**, not by missing client functionality;
|
||||
that planner gap is now closed end-to-end (30/45 on 0.20.44 -> 45/45 on 0.20.45).
|
||||
|
||||
## 12. Test prompts (~40)
|
||||
|
||||
See `docs/QUICKEDIT_PROMPTS.md` and the runner
|
||||
`tests/e2e/photo-editor/quickedit-coverage.js` for per-prompt pass/fail.
|
||||
|
||||
### Live run (build 0.20.45) — 45/45 PASS
|
||||
|
||||
All 45 prompts plan to the exact expected op. HARNESS 0.20.45 routes these
|
||||
structural ops server-side: `fill.flood`, `text.edit`, `layers.align`,
|
||||
`layer.rotate`/`layer.flip`, `document.rotate`/`document.flip`,
|
||||
`selection.copy`/`selection.paste`, `layers.flatten`, `layers.mergeDown` (plus
|
||||
the previously passing set). Results:
|
||||
`/tmp/opencode/qe-coverage45/quickedit-coverage.json`.
|
||||
|
||||
### Previous run (build 0.20.44) — 30/45 PASS
|
||||
|
||||
Kept for provenance. Failures were all the server planner emitting a generic
|
||||
`adjustments.apply`/`transform.apply` or a wrong op; ids 1, 2, 3, 4, 8, 9, 10,
|
||||
11, 12, 13, 14, 19, 20, 40, 41. All now fixed in 0.20.45.
|
||||
|
||||
|
||||
## Provenance
|
||||
|
||||
Recon inventory captured from `static/js/editor/build/topbar.js`,
|
||||
`static/js/editor/build/toolbar.js`, `static/js/editor/canvas-context-menu.js`,
|
||||
`static/js/galleryEditor.js` (`installEditorControl`), `static/js/editor/nl-edit.js`,
|
||||
`routes/editor_agent_routes.py`, and `src/editor_style_guide.py`.
|
||||
@@ -0,0 +1,38 @@
|
||||
# Odysseus maintainer handbook
|
||||
|
||||
Odysseus maintainers are trusted to act, not to wait for permission. The default is individual responsibility; additional review is required when the **risk** warrants it.
|
||||
|
||||
This handbook records the project's maintainer governance and gives reviewers a reusable process. It complements [CONTRIBUTING.md](../../CONTRIBUTING.md) and [SECURITY.md](../../SECURITY.md); it does not replace the PR template, repository protections, or product decisions.
|
||||
|
||||
## At a glance
|
||||
|
||||
| Area | Working rule |
|
||||
|---|---|
|
||||
| Product direction | Pewds is the Product Owner |
|
||||
| Ordinary, reversible maintainer PR | Self-merge allowed after appropriate validation and applicable repository checks |
|
||||
| External contribution | Maintainer review required |
|
||||
| High-risk change | At least one **independent, qualified** maintainer review |
|
||||
| Access | Need-to-have, least privilege, separately granted per system |
|
||||
| Release | A designated Release Maintainer owns preparation and publish |
|
||||
| Inactivity | Access review after 2 months without activity, notice, or response |
|
||||
| Documentation | Repository Markdown is canonical |
|
||||
|
||||
These are governance rules, not guarantees that a GitHub permission or branch-protection setting permits a particular action. Respect the stronger applicable automated gate.
|
||||
|
||||
## Start here
|
||||
|
||||
- [Roles and access](roles-and-access.md): responsibility, permissions, and continuity.
|
||||
- [Product and feature proposals](product-and-features.md): product approval versus technical ownership.
|
||||
- [Changes and review](changes-and-review.md): review thresholds, self-merge, contributor corrections.
|
||||
- [Practical review playbook](review-playbook.md): risk, provenance, CI, CodeQL, independent audit, and handoff evidence.
|
||||
- [Triage](triage.md): turning incoming reports into actionable work.
|
||||
- [Testing and releases](testing-and-releases.md): evidence, platform validation, promotion to main.
|
||||
- [Documentation](documentation.md): canonical sources, updates, and traceability.
|
||||
|
||||
## Accountability
|
||||
|
||||
Maintainers own the changes they take on, including validation, regression prevention, follow-up, and rollback when needed. Roles describe responsibilities, not a hierarchy of technical approval.
|
||||
|
||||
Important product-direction decisions go through the Product Owner. Maintainers retain responsibility for feasibility, implementation, maintainability, and security.
|
||||
|
||||
AI coding or review agents can help collect evidence, test hypotheses, and prepare changes, but they are not the qualified human reviewer required for high-risk changes. A maintainer must inspect and accept the evidence.
|
||||
@@ -0,0 +1,62 @@
|
||||
# Changes and review
|
||||
|
||||
Review is **risk-based**. Trusted maintainers should be able to ship ordinary work without creating a queue of ceremonial approvals.
|
||||
|
||||
## Required review
|
||||
|
||||
| Change | Minimum review rule |
|
||||
|---|---|
|
||||
| Ordinary, reversible maintainer change | Independent approval optional; self-merge permitted after appropriate validation |
|
||||
| External contribution | Maintainer review required |
|
||||
| High-risk change | At least one independent, qualified maintainer review required |
|
||||
|
||||
Repository branch protection and required checks also apply. Never treat this document as permission to bypass a live protection rule.
|
||||
|
||||
## Ordinary maintainer changes
|
||||
|
||||
1. Open a PR against `dev`, scoped to one coherent change.
|
||||
2. Explain intent, expected behavior, validation, and rollback if relevant.
|
||||
3. Run suitable tests and inspect the actual result.
|
||||
4. Ensure applicable required checks pass, address feedback, and merge when ready.
|
||||
|
||||
Self-merge is allowed for ordinary reversible work. The author remains responsible for follow-up, correction, and rollback.
|
||||
|
||||
## External contributions and small corrections
|
||||
|
||||
External contributions receive maintainer review.
|
||||
|
||||
A maintainer may directly fix a **small, obvious** problem in a contributor PR when the intended behavior is clear and the repair is faster than a separate round trip. Document what changed, preserve contributor attribution and history, avoid force-pushing, and re-run affected checks.
|
||||
|
||||
Substantive or ambiguous corrections require discussion with the contributor or a separately scoped correction. A maintainer must not silently turn a contributor's PR into unrelated work. Any corrective commit changes the SHA being reviewed; update validation and audit evidence accordingly.
|
||||
|
||||
## High-risk changes
|
||||
|
||||
High-risk work requires **at least one independent qualified review**. The author cannot be the only approver. Examples:
|
||||
|
||||
- Authentication, authorization, tenancy, sandboxing, agent tool permissions, or secrets.
|
||||
- Outbound network and SSRF defenses, sensitive integrations, and deployment access.
|
||||
- Destructive operations, deletion, filesystem ownership, and data migrations.
|
||||
- Persistence and concurrency invariants with possible data loss.
|
||||
- Major architectural contracts, release infrastructure, or changes hard to roll back.
|
||||
|
||||
A high-risk review should verify the threat or failure model, exact final commit, reproducer or regression evidence, failure and rollback behavior, and the relevant CI/security findings. Use the [review playbook](review-playbook.md).
|
||||
|
||||
## Cross-area changes and CODEOWNERS
|
||||
|
||||
Touching multiple areas does **not** automatically require multiple approvals. Consult domain expertise when needed. `.github/CODEOWNERS` currently contains no ownership rules; the per-area map is under discussion in [issue #593](https://github.com/odysseus-dev/odysseus/issues/593). Once populated, it helps identify reviewers and accountable contacts; it is not a blanket approval requirement by itself.
|
||||
|
||||
## Validation and UI evidence
|
||||
|
||||
Automated tests count as runtime evidence when they truly exercise the changed behavior. Use manual validation when coverage is insufficient.
|
||||
|
||||
For any change to visible UI behavior, follow the current [contributor UI checklist](../../CONTRIBUTING.md#style-and-visual-changes) and the repository PR template. Screenshots or clips are expected when they materially demonstrate the rendered result; screenshots alone do not replace behavioral tests.
|
||||
|
||||
## Disagreement and escalation
|
||||
|
||||
The maintainer doing the work owns ordinary implementation decisions within approved product direction and existing technical contracts. For substantive disagreements:
|
||||
|
||||
1. Involve a qualified maintainer with relevant expertise.
|
||||
2. Escalate unresolved cross-cutting technical issues to trusted project-wide maintainers.
|
||||
3. Refer product-direction disagreements to the Product Owner.
|
||||
|
||||
Security findings should be handled via [SECURITY.md](../../SECURITY.md), without disclosing exploit details in public issues. Repository protection rules should support these risk-based policies and should not accidentally force independent approval for every ordinary maintainer PR.
|
||||
@@ -0,0 +1,32 @@
|
||||
# Documentation
|
||||
|
||||
Repository Markdown is the canonical source for project documentation. A public docs site may be generated from it; avoid maintaining an independent wiki copy unless there is a deliberate future decision to do so.
|
||||
|
||||
## Where information belongs
|
||||
|
||||
| Location | Purpose |
|
||||
|---|---|
|
||||
| `/design` | Product intent, proposals, and accepted decisions (when those paths are used) |
|
||||
| `/specs` | Technical contracts and invariants |
|
||||
| `/website` | User, setup, configuration, and deployment guidance (published as the docs site) |
|
||||
| `/docs` | Developer, architecture, security, and maintainer guidance |
|
||||
| Root Markdown | Discoverable entry points and policies |
|
||||
|
||||
Keep root documents easy to find:
|
||||
|
||||
- `README.md`
|
||||
- `CONTRIBUTING.md`
|
||||
- `SECURITY.md`
|
||||
- `THREAT_MODEL.md`
|
||||
|
||||
Not every named directory exists today; do not invent a parallel documentation store just to match this map.
|
||||
|
||||
## Updating documentation
|
||||
|
||||
When behavior, architecture, a workflow, or its verification requirements change, update the canonical documentation in the same PR when appropriate.
|
||||
|
||||
Separate **policy** from **examples**: examples may evolve with tooling; changes to role authority, review requirements, or product ownership require an explicit governance decision. Do not introduce a new mandatory approval rule through a checklist alone.
|
||||
|
||||
Documentation PRs should link the relevant issue, record what was reviewed (links, commands, or affected features), and avoid publishing private channels, account names, local paths, secrets, or confidential incident details.
|
||||
|
||||
For practical examples of review evidence, see the [review playbook](review-playbook.md). For contributor setup and PR requirements, use [CONTRIBUTING.md](../../CONTRIBUTING.md).
|
||||
@@ -0,0 +1,48 @@
|
||||
# Product and feature proposals
|
||||
|
||||
Pewds ([@pewdiepie-archdaemon](https://github.com/pewdiepie-archdaemon)) is the Product Owner for Odysseus and has final authority over product direction. Maintainers own technical feasibility, implementation, and safety.
|
||||
|
||||
## Which changes require product approval?
|
||||
|
||||
**Product Owner approval before implementation:** substantial new features, changes to product direction, and product changes that are hard to reverse.
|
||||
|
||||
**No separate product approval by default:** bug fixes, security fixes, maintenance, tests, CI, documentation, internal refactors, and small, reversible product changes within existing direction.
|
||||
|
||||
Maintainers may make those smaller product decisions. Pewds can ask for changes or a rollback; the maintainer owns safe rollback or recovery.
|
||||
|
||||
### Urgent security and reliability fixes
|
||||
|
||||
Urgent fixes or containment may proceed without prior product approval, even if behavior temporarily changes. Keep the change to what stops the immediate problem, notify Pewds promptly, and record the reason and rollback or recovery plan. Agree on any lasting product-direction change with Pewds afterwards. Normal high-risk review and repository checks still apply.
|
||||
|
||||
## Product Owner initiated
|
||||
|
||||
```text
|
||||
Product Owner proposal or implementation
|
||||
-> technical feasibility
|
||||
-> plan and implementation
|
||||
-> risk-based review and validation
|
||||
```
|
||||
|
||||
A Product Owner proposal is already product-approved; it is not automatically technically approved or exempt from high-risk review.
|
||||
|
||||
## Community or maintainer initiated
|
||||
|
||||
Community proposals start as a Feature Request issue. Check [ROADMAP.md](../../ROADMAP.md) and existing issues first; take early ideas to [Discussions](https://github.com/odysseus-dev/odysseus/discussions/categories/ideas).
|
||||
|
||||
```text
|
||||
Feature proposal
|
||||
-> Product Owner accepts / rejects, where approval is required
|
||||
-> technical feasibility
|
||||
-> plan and implementation
|
||||
-> risk-based review and validation
|
||||
```
|
||||
|
||||
Record the decision in an issue, proposal, work item, or PR so future maintainers can discover it. Do not rely on a chat-only decision.
|
||||
|
||||
## Decision boundaries
|
||||
|
||||
The Product Owner decides **what belongs in Odysseus**. Maintainers decide **how approved work is built safely and maintainably**.
|
||||
|
||||
A rejected or out-of-scope feature can be maintained independently in a contributor's fork.
|
||||
|
||||
If the Product Owner is unavailable, work requiring prior product approval waits unless authority was explicitly delegated. Smaller decisions and urgent fixes follow the rules above.
|
||||
@@ -0,0 +1,129 @@
|
||||
# Practical PR review playbook
|
||||
|
||||
Use this as a **repeatable review method**, not as an extra organizational approval ladder. The binding minimums remain in [changes and review](changes-and-review.md): ordinary maintainer changes may be self-merged, external PRs require maintainer review, and high-risk work requires at least one independent qualified maintainer review.
|
||||
|
||||
No single checklist replaces engineering judgment. Increase depth with risk.
|
||||
|
||||
## 1. Understand the PR and establish its review gates
|
||||
|
||||
- Establish the problem, what the PR fundamentally proposes, and why the approach makes sense.
|
||||
- Check product direction, architecture fit, and scope before detailed review.
|
||||
- Write down the questions the review must answer, the evidence needed, and what would block further review or approval.
|
||||
- Confirm repository, source fork, target branch (`dev`), exact **HEAD SHA**, and current base.
|
||||
- Review the full effective diff, commit history, issue, prior feedback, and related upstream changes.
|
||||
- Confirm the branch is mergeable against the **current** `dev`, not an older local snapshot.
|
||||
- Identify the user-facing contract, affected data and users, security boundaries, and rollback plan.
|
||||
- Separate pre-existing upstream problems from regressions introduced or made reachable by the PR.
|
||||
|
||||
Example read-only commands:
|
||||
|
||||
```bash
|
||||
REPO="odysseus-dev/odysseus"
|
||||
PR=1234
|
||||
gh pr view "$PR" --repo "$REPO" --json title,state,isDraft,headRefOid,baseRefName,mergeable
|
||||
gh pr diff "$PR" --repo "$REPO"
|
||||
gh pr checks "$PR" --repo "$REPO"
|
||||
```
|
||||
|
||||
A green check on an old commit does not verify the revised branch.
|
||||
|
||||
## 2. Review the failure and threat model
|
||||
|
||||
For the relevant change, ask what happens with malformed input, missing auth state, wrong owner, concurrent requests, stale caches, partial failure, retries, cancelled jobs, unavailable dependencies, and restart/recovery.
|
||||
|
||||
Prioritize boundaries that can lose data or authority: identity, delegated API tokens, filesystem confinement, email deletion, persistence versioning, network/SSRF destinations, secrets, and agent tool execution.
|
||||
|
||||
For security claims, demonstrate the source-to-sink path and the *actual* guard. Tests must represent production middleware or transport where a unit mock could conceal a bypass.
|
||||
|
||||
## 3. Correct without obscuring contributor work
|
||||
|
||||
For small, unambiguous issues, maintainers may add focused corrections directly to a contributor PR under [the contribution review policy](changes-and-review.md#external-contributions-and-small-corrections). Preserve provenance, explain the change, prefer normal commits and fast-forward pushes, and avoid unrelated rewrites.
|
||||
|
||||
For substantive or ambiguous work, ask the contributor or prepare a clearly separated patch. Any code change means validating **the new SHA**, not approving based solely on the previous audit.
|
||||
|
||||
## 4. Build an evidence record
|
||||
|
||||
Run contributor code under the [runtime isolation rules](testing-and-releases.md#runtime-isolation).
|
||||
|
||||
Useful evidence includes:
|
||||
|
||||
- Pre-fix reproduction, expected outcome, and actual outcome.
|
||||
- Focused regression tests for the fix and broad suites relevant to cross-cutting effects.
|
||||
- Actual runtime checks (browser/UI, backend, Docker, supported OS) where necessary.
|
||||
- Exact executed commands, result counts, skipped cases, and environmental limitations.
|
||||
- GitHub required checks and conflict-free state on the exact final SHA.
|
||||
- Relevant operational consequences (data migration, rollback, compatibility).
|
||||
|
||||
Keep audits reproducible. A screenshot alone is not a functional test, and a passing mocked test is not proof that a production integration path is safe.
|
||||
|
||||
## 5. Independent review for high-risk changes
|
||||
|
||||
A separate **qualified maintainer** must inspect high-risk changes; the implementing author cannot be the sole reviewer. Independent coding agents may be useful for adversarial tests and cross-checks, but **an AI agent is not itself a GitHub approver** and does not replace human accountability.
|
||||
|
||||
To avoid confirmation bias, give the independent reviewer the current SHA, changed files, acceptance criteria, known reproduction, and explicit permission to challenge the proposed solution. Preserve unresolved findings and require a new audit when meaningful fixes change the diff.
|
||||
|
||||
## CodeQL and security scanning
|
||||
|
||||
A successful CodeQL workflow means analysis ran, not that the security dashboard is empty. Likewise, resolving a PR discussion does not necessarily resolve its code-scanning alert.
|
||||
|
||||
For each relevant alert:
|
||||
|
||||
1. Record the alert number, rule, severity, file/line, state, and source-to-sink trace if applicable.
|
||||
2. Compare with the current `dev` baseline; decide whether the PR introduced, exposed, or did not affect it.
|
||||
3. Reproduce or reason through the actual production behavior, including validation and authorization.
|
||||
4. **Fix** credible vulnerabilities, preferably with a regression test. Do not hide them by broad suppressions.
|
||||
5. **Recommend dismissal** only for a demonstrable false positive or a stale/invalid finding, with a precise technical explanation; only authorized maintainers should dismiss alerts.
|
||||
6. Document genuine but intentionally accepted risk separately. A true positive is not a false positive merely because it is deliberate.
|
||||
7. Rerun or inspect updated analysis on the final commit and confirm no blocking issues remain.
|
||||
|
||||
Never publish exploit payloads, secrets, or credentials in public review comments. See [SECURITY.md](../../SECURITY.md).
|
||||
|
||||
## 6. Decide and integrate
|
||||
|
||||
Before approval or self-merge, verify:
|
||||
|
||||
- Current branch and reviewed SHA are still the ones to be merged.
|
||||
- All applicable required checks are complete and successful.
|
||||
- High-risk independent review is present, where required.
|
||||
- No unresolved, credible P0/P1/P2 security or data-integrity defects remain in the change. Resolve or explicitly document P3 observations with a tracked owner and priority.
|
||||
- Review discussions, compatibility risks, and operational handoffs have been addressed.
|
||||
- The correct target is `dev`; `main` is curated for releases.
|
||||
|
||||
This P0-P3 classification is [triage guidance](triage.md#practical-priority-language), not a replacement for branch protection or an excuse to self-approve high-risk work. Maintainers may apply a stricter bar where warranted.
|
||||
|
||||
For a final manual merge, GitHub CLI supports an exact-head guard:
|
||||
|
||||
```bash
|
||||
gh pr merge 1234 --repo odysseus-dev/odysseus --squash \
|
||||
--match-head-commit <reviewed-full-head-sha>
|
||||
```
|
||||
|
||||
Use `--delete-branch` only when removing the source branch is appropriate and does not disrupt another workstream. Do not force a merge past review or security requirements.
|
||||
|
||||
## 7. Post-merge follow-up
|
||||
|
||||
Verify the PR actually merged and reference its integration commit. Record remaining operational work, such as schema migrations, backup/restore verification, service restarts, or compatibility documentation. A code change to a container mount does **not** by itself migrate existing runtime data.
|
||||
|
||||
Do not silently mark a deferred data migration complete; track its owner, evidence, rollback, and execution separately.
|
||||
|
||||
## Reusable reviewer handoff
|
||||
|
||||
```text
|
||||
PR / target:
|
||||
Reviewed source HEAD SHA:
|
||||
Current target branch SHA:
|
||||
Scope and changed files:
|
||||
Impact and risk classification:
|
||||
Security / correctness invariants challenged:
|
||||
Pre-fix reproducer and post-fix evidence:
|
||||
Tests run, skipped, and environment:
|
||||
CodeQL / scanning alert dispositions:
|
||||
Required GitHub checks (exact SHA):
|
||||
Mergeability and remaining conflicts:
|
||||
Unresolved P0 / P1 / P2 / P3 findings:
|
||||
Rollback / operational follow-ups:
|
||||
Independent qualified review required? Completed?
|
||||
Verdict: APPROVED / CHANGES REQUIRED / BLOCKED
|
||||
```
|
||||
|
||||
State what was actually verified. Do not write "all checks green" if only a subset of workflows was inspected.
|
||||
@@ -0,0 +1,40 @@
|
||||
# Roles and access
|
||||
|
||||
Roles clarify who is accountable. They do not create a management chain or automatically grant privileges.
|
||||
|
||||
## Responsibility and typical access
|
||||
|
||||
| Responsibility | Typical GitHub access | Purpose |
|
||||
|---|---|---|
|
||||
| Product Owner | No automatic repository access implied | Product direction |
|
||||
| Organization Owner | Organization Owner | Organization administration and continuity |
|
||||
| Repository Admin | Admin | Repository settings, permissions, and protection rules |
|
||||
| Maintainer | Write by default | Implement, review, merge, and maintain |
|
||||
| Triage | Triage | Organize intake, de-duplicate, and prepare work |
|
||||
| Contributor / Platform Tester | Public / Read as appropriate | Contribute code or provide validation |
|
||||
|
||||
Maintain access may be granted when repository-management duties require it.
|
||||
|
||||
## Maintainer focus
|
||||
|
||||
- **Core Maintainer:** project-wide judgment, difficult technical decisions, governance support.
|
||||
- **Technical Maintainer:** implementation, architecture, validation, technical health.
|
||||
- **Project Maintainer:** review, contributor coordination, documentation, releases, organization.
|
||||
|
||||
These are responsibility labels. No label automatically changes GitHub permissions or overrides the [review rules](changes-and-review.md).
|
||||
|
||||
## Functional responsibilities
|
||||
|
||||
Maintainers may coordinate Release, Security, Infrastructure and CI, Community, or Test and Validation. These are duties, not ranks. For each release, designate one Release Maintainer and record the handoff if ownership changes.
|
||||
|
||||
## Least privilege and continuity
|
||||
|
||||
Grant access because it is necessary for current work, not because of a title. GitHub, Matrix, Plane, infrastructure, SSH, deployment systems, secrets, and external services are separate access surfaces.
|
||||
|
||||
For elevated or sensitive access, document a responsible owner and recovery route in an appropriate **private** administrative system. Do not publish tokens, SSH details, personal addresses, or credential locations in this handbook. Remove access promptly when it is no longer needed.
|
||||
|
||||
## Inactivity
|
||||
|
||||
Tell an admin when you expect to be unavailable; a communicated absence is fine.
|
||||
|
||||
If there is no meaningful maintainer or triage activity for **two months**, no prior notice, and no response to reasonable follow-up, elevated access may be reviewed and removed. This is access hygiene, not punishment. Returning members may regain access appropriate to their active responsibilities.
|
||||
@@ -0,0 +1,52 @@
|
||||
# Testing and releases
|
||||
|
||||
Validation must match the actual change, and evidence must state what was **run**, **observed**, and **not tested**.
|
||||
|
||||
`dev` is the integration and stabilization branch. `main` is the curated release destination. Changes land in `dev`; a release promotes a verified `dev` revision to `main`.
|
||||
|
||||
## Runtime isolation
|
||||
|
||||
Run contributor-controlled tests, builds, installs, and repros in an isolated, secretless environment. Use disposable containers or microVMs where appropriate, with no credentials, sensitive host files, production data, or host/container-management sockets exposed. If safe isolation is unavailable, record the check as not run.
|
||||
|
||||
Agents may use a skill for the maintainer's preferred environment and handling; the same isolation requirements apply.
|
||||
|
||||
## Change validation
|
||||
|
||||
- Run focused tests that cover the modified behavior and failure cases.
|
||||
- Run broader regressions where risk crosses module boundaries.
|
||||
- For authentication, data loss, or similar high-risk work, include adversarial and rollback tests.
|
||||
- Reproduce the issue on the pre-fix version when safe and practical, then prove the correction.
|
||||
- For visual changes, run the application and attach the screenshots or clips required by [CONTRIBUTING.md](../../CONTRIBUTING.md).
|
||||
- Record exact commands, environment, result, skipped tests, and blockers. Never claim execution from a command you did not run.
|
||||
|
||||
GitHub Actions success means a workflow finished successfully; it does **not** mean CodeQL found no alerts. Use [review playbook](review-playbook.md#codeql-and-security-scanning) to assess findings separately. A review must tie required CI and the final diff to the **same commit SHA**.
|
||||
|
||||
## Supported-environment evidence
|
||||
|
||||
Platform Testers may validate without maintainer permissions. Useful coverage includes:
|
||||
|
||||
- Windows native and WSL.
|
||||
- macOS Apple Silicon.
|
||||
- macOS Intel while supported.
|
||||
- Linux native on the reference distribution.
|
||||
- Docker on supported hosts.
|
||||
|
||||
Record the environment and configuration for each result. Do not imply that a platform was tested when it was not.
|
||||
|
||||
## Release ownership
|
||||
|
||||
A designated **Release Maintainer** owns preparation and the publish decision once the release criteria are met. Other maintainers and testers supply the evidence needed for the change set. If the Release Maintainer becomes unavailable, another maintainer explicitly takes over.
|
||||
|
||||
## Release checklist
|
||||
|
||||
Before promoting `dev` to `main`:
|
||||
|
||||
1. Identify and freeze the release candidate SHA; check the actual content that will ship.
|
||||
2. Verify required checks and relevant supported-environment validation.
|
||||
3. Confirm high-risk and data-migration findings are resolved or addressed through an explicit approved plan.
|
||||
4. Document skipped coverage, known issues, compatibility and upgrade implications.
|
||||
5. Prepare the version, tag, artifacts, and release notes.
|
||||
6. Confirm rollback/recovery, including any manual persistence or data migration steps.
|
||||
7. Publish and record what was promoted, the verification evidence, and operational follow-ups.
|
||||
|
||||
Publishing does not require ceremonial Core or domain sign-off when the criteria are met. Exceptional or disputed releases follow the normal high-risk escalation path.
|
||||
@@ -0,0 +1,48 @@
|
||||
# Triage
|
||||
|
||||
Triage turns reports into evidence-backed work that a maintainer can pick up without guessing.
|
||||
|
||||
## Intake flow
|
||||
|
||||
```text
|
||||
Report
|
||||
-> reproduce / clarify
|
||||
-> identify duplicates and affected versions
|
||||
-> classify impact, urgency, and risk
|
||||
-> prepare actionable work item
|
||||
-> maintainer takes ownership
|
||||
-> PR, verification, and follow-up
|
||||
```
|
||||
|
||||
## A useful work item
|
||||
|
||||
Record what is known and distinguish it from what remains unverified:
|
||||
|
||||
- Original report and reproducible steps; installation method, OS, version/commit, and relevant environment.
|
||||
- Expected versus actual behavior, including user-visible impact.
|
||||
- Evidence: sanitized logs, screenshots, failing tests, or controlled reproduction.
|
||||
- Related issues, PRs, duplicates, or a known prior regression.
|
||||
- Scope, acceptance criteria, and non-goals.
|
||||
- Priority, ownership, and relevant domain expertise.
|
||||
- Remaining unknowns, risks, and safe recovery or workaround when available.
|
||||
|
||||
Small and obvious fixes may go straight to a focused PR, consistent with [CONTRIBUTING.md](../../CONTRIBUTING.md) and [changes and review](changes-and-review.md).
|
||||
|
||||
## Practical priority language
|
||||
|
||||
Use P0-P3 as **triage shorthand**, not as a substitute for verified exploitability or the project's actual GitHub labels:
|
||||
|
||||
| Priority | Typical impact | Expected response |
|
||||
|---|---|---|
|
||||
| P0 | Active exploitation, broad irreversible loss, catastrophic outage | Immediate containment and escalation |
|
||||
| P1 | Credible severe security or data-integrity vulnerability | Urgent owner assignment and validated fix |
|
||||
| P2 | Significant correctness, security, or operational regression | Prioritize and resolve before approving affected high-risk changes |
|
||||
| P3 | Limited impact, maintainability, or low-risk polish | Fix or explicitly track with rationale |
|
||||
|
||||
Do not call a concern harmless merely because an automated check is green. Escalate privately when reproduction would disclose a vulnerability.
|
||||
|
||||
## Where decisions live
|
||||
|
||||
Matrix is for coordination; durable decisions belong in GitHub, Plane, a specification, or the relevant PR. Keep confidential security and access details in appropriate private channels. Do not leave important project decisions only in chat history.
|
||||
|
||||
See the [review playbook](review-playbook.md) for converting triage into review evidence and handling contested or uncertain findings.
|
||||
@@ -0,0 +1,39 @@
|
||||
# Upstream SearXNG trust validation
|
||||
|
||||
SearXNG owns independent system and Python certificate stores. Updating Odysseus's Python environment does not update this sidecar. The base and both standalone GPU Compose files now select the official `docker.io/searxng/searxng:2026.10.24-ef0ec03da` image at immutable multiarch digest `sha256:7c7cbe509429e82755104b2a95f52085a98633c95d53f44e4e905fa3a59f08c8`. Its system and certifi stores remove the affected eMudhra trust. The [upstream certifi update](https://github.com/searxng/searxng/pull/6881) is included in this release.
|
||||
|
||||
Deploy the upstream image directly. The validation Dockerfile adds public fixtures and diagnostics only; no repair layer, custom runtime image or project registry publisher is required. `scripts/check_ca_trust.py` owns the known-certificate inventory for both the application and SearXNG checks.
|
||||
|
||||
## Validate a pin update
|
||||
|
||||
Run from the repository root on a disposable Docker host. The helper uses a fresh configuration volume, omits published ports and does not load the operator's `.env`. It retains the base Compose settings wrapper, healthcheck and capabilities. Outbound network access and working search engines are required for the live checks.
|
||||
|
||||
```sh
|
||||
image=$(docker compose --env-file /dev/null -f docker-compose.yml config --format json |
|
||||
python3 -c 'import json, sys; print(json.load(sys.stdin)["services"]["searxng"]["image"])')
|
||||
|
||||
# Offline build-step checks, including actual entrypoint and loopback health.
|
||||
# Pulling the selected base still requires registry access.
|
||||
docker build --network=none --target validation \
|
||||
--build-arg "SEARXNG_IMAGE=$image" -f docker/searxng/Dockerfile .
|
||||
|
||||
# Exercise the exact unmodified deployment image after an anonymous pull.
|
||||
docker pull "$image"
|
||||
bash .github/scripts/validate-searxng-image.sh "$image"
|
||||
```
|
||||
|
||||
The offline checks require all fourteen incident chains to fail certificate verification through the system PEM bundle, hashed CA directory and certifi. The affected positive-control roots must accept those same chains. Each fixture's DER fingerprint is verified first, and a fixed verification timestamp prevents expiry from hiding a broken mitigation. Checks run again after the real upstream root entrypoint regenerates trust.
|
||||
|
||||
The live checks use SearXNG's actual curl client factory. Ordinary verified HTTPS must succeed, all fourteen incident hosts must fail with curl certificate error 60, and a JSON search must return results. DNS failures, timeouts and generic handshake failures are inconclusive and fail validation. The checks inspect the already-running service without changing its trust stores.
|
||||
|
||||
The `ci / SearXNG trust` workflow runs these offline and Compose/live checks on native amd64 and arm64 runners. It selects the same immutable image as base Compose, pulls without registry credentials, and has read-only repository permissions. It neither publishes a replacement image nor requires package-write credentials. Update all three deployment pins together; the alignment regression guards the standalone GPU files.
|
||||
|
||||
## Rollout and scope
|
||||
|
||||
Changing a Compose pin does not replace running containers. After updating the checkout, run `docker compose pull searxng` and `docker compose up -d --no-deps searxng`, then inspect health, retained settings and actual Odysseus search integration. Use the appropriate standalone GPU Compose file when that is the deployment being managed.
|
||||
|
||||
Operator-mounted trust, custom engines, persisted packages and independently managed clients need separate effective-client checks. An amd64 result does not establish arm64 behavior. The fixture-based check detects known certificate fingerprints; it does not model every native revocation mechanism or arbitrary reissued certificates sharing a compromised key. See [outbound TLS trust](tls-trust.md) for the application, bootstrap, Chromium and other runtime boundaries.
|
||||
|
||||
## Public fixture provenance
|
||||
|
||||
The public CA inventory comes from the incident's [roots](https://emudhra.we-have-a-problem.com/roots.json) and [intermediates](https://emudhra.we-have-a-problem.com/impacted-intermediates.json). The additional cross-signed issuer and fourteen chain fixtures come from [chains.zip](https://emudhra.we-have-a-problem.com/chains.zip), SHA-256 `7b949956955fc501a30f4d98d2f513d34c0bbbe92e29a968194c96e097ce2345`. Fixtures contain public certificates only. See [the fixture notes](../tests/fixtures/ca-trust/README.md).
|
||||
@@ -0,0 +1,75 @@
|
||||
# Outbound HTTPS trust
|
||||
|
||||
Odysseus uses both Python package CA bundles and system trust. HTTPX uses certifi by default; the guarded web/PDF fetcher and webhook delivery explicitly use Python's system SSL context. A configured `LLM_CA_BUNDLE` adds certificates to system trust for model calls. Upgrading certifi alone cannot repair every outbound HTTPS path.
|
||||
|
||||
For the October 2026 eMudhra incident, use certifi 2026.10.23 or newer, pip 26.2.2 or newer, and uv 0.13.5 or newer wherever uv is used. Pip has its own vendored certifi and also loads system roots. Update the operating system's certificate package separately. See the [Python security announcement](https://discuss.python.org/t/109576) and [incident details](https://emudhra.we-have-a-problem.com/).
|
||||
|
||||
Before other pip installs, bootstrap the selected environment with the integrity-pinned wheels:
|
||||
|
||||
```sh
|
||||
python -m pip install --require-hashes --only-binary=:all: --no-deps -r requirements-bootstrap.txt
|
||||
python -m pip install -r requirements.txt
|
||||
```
|
||||
|
||||
The pinned bootstrap protects package integrity even when the old installer's certificate bundle is affected. It is not an operating-system trust-store update. Native Windows/macOS trust and command-line OpenSSL PEM stores may differ; follow the platform vendor's remediation for every store in use. Restart Odysseus and its subprocesses after updating trust.
|
||||
|
||||
On Linux/OpenSSL deployments, inspect both the effective CA file and hashed CA directory, plus the two Python bundles:
|
||||
|
||||
```sh
|
||||
python scripts/check_ca_trust.py --system --python --overrides --verbose
|
||||
```
|
||||
|
||||
Explicit PEM bundles can also be passed as positional arguments. The check rejects the four published root fingerprints, all sixteen published intermediate certificates (including their cross-signs), the cross-signed G3 issuer in the published test chains, and malformed/empty bundles. Checking intermediates matters because adding one as a custom trust anchor can restore an affected chain even after its roots have been removed.
|
||||
|
||||
`--overrides` checks every nonempty `LLM_CA_BUNDLE`, `SSL_CERT_FILE`, `REQUESTS_CA_BUNDLE`, `CURL_CA_BUNDLE`, `PIP_CERT`, `NODE_EXTRA_CA_CERTS` and `GRPC_DEFAULT_SSL_ROOTS_FILE_PATH` setting in the checker's process environment, including configured fallback settings. Requests/pip hashed-directory overrides are inspected too; `--system` covers `SSL_CERT_DIR`. Run with the application's environment and filesystem access. The script does not load `.env` or application configuration files; pass any CA paths configured only there explicitly, for example `python scripts/check_ca_trust.py --system --python /path/to/private-ca.pem`. Missing or invalid configured files fail the check. Selecting only `--overrides` with no configured paths also fails instead of reporting that stores were validated.
|
||||
|
||||
`--verbose` reports the Python executable/prefix, user home, effective UID/GID where available, and resolved CA paths, including bundles selected from user-installed packages. Compare these with the running application's identity and interpreter. It does not dump environment variables or credentials.
|
||||
|
||||
This is an offline inventory check of known certificate fingerprints. It does not modify stores, reject requests at runtime, model native OS revocation, or detect arbitrary reissued certificates sharing the compromised keys. Chromium's policy separately matches public keys. Check mounted or replaced CA files again after deployment, remove affected trust through the owning platform/client configuration, and restart clients that cache SSL contexts. A passing build cannot validate custom files introduced later.
|
||||
|
||||
Both Docker build stages use Python 3.14.9, refresh system CA certificates and bootstrap pip/certifi before dependency installation. The build fails if known affected CA certificates remain. Node uses the corrected system store through `--use-openssl-ca`. Python gRPC uses that same PEM file through `GRPC_DEFAULT_SSL_ROOTS_FILE_PATH`; updating certifi alone does not replace gRPC's independent wheel bundle. After dependency installation, the image checks gRPC's selected file as well. An explicit gRPC override must be readable and free of known affected CA certificates. [grpcio 1.84.1](https://github.com/grpc/grpc/releases/tag/v1.84.1) includes the upstream root-bundle correction and is the application minimum. Retain the checked override as a consistent image trust selection; older persisted Python environments and explicit client roots still need independent checks.
|
||||
|
||||
The image also bundles Docker CLI [29.9.2](https://github.com/moby/moby/releases/tag/docker-v29.9.2), built with Go 1.26.10's distrust of the compromised intermediate keys. Cookbook uses this client for optional Docker operations. Default Compose does not mount the host Docker socket; local daemon access requires the explicit [host-Docker overlay](../docker/host-docker.yml), whose usual Unix-socket connection does not use TLS. The client update is maintenance of a shipped tool, not evidence that these local calls were exposed. It does not update the host daemon or other containers' clients and trust stores; assess those separately.
|
||||
|
||||
Chromium uses a separate trust store. The Docker image installs a managed `CADistrustedCertificates` policy for the eight compromised intermediate public keys, covering their cross-signs without depending on background revocation updates. It adds no trusted certificate and does not disable certificate verification. The policy and public-certificate provenance are documented in [the browser policy note](../docker/chromium-ca-policy.md). Native browser installations and independently managed profiles require their own platform policy or vendor update; this image policy does not configure host browsers.
|
||||
|
||||
To replace only Odysseus in an already running Compose stack, build from the corrected checkout with a distinct local image tag. Use the same Compose files and project name as the existing deployment for every command below:
|
||||
|
||||
```sh
|
||||
export ODYSSEUS_IMAGE="odysseus-local:ca-$(git rev-parse --short=12 HEAD)"
|
||||
docker compose build --pull --no-cache odysseus
|
||||
docker compose up -d --no-deps --pull never --no-build odysseus
|
||||
docker image inspect --format '{{.Id}}' "$ODYSSEUS_IMAGE"
|
||||
docker inspect --format '{{.Image}}' "$(docker compose ps -q odysseus)"
|
||||
```
|
||||
|
||||
The two image IDs must match. Keep the selected `ODYSSEUS_IMAGE` in the deployment configuration for subsequent recreations. Compose can prefer the registry image when both `image` and `build` are present; the explicit pull/build flags keep this replacement on the local build. `--no-deps` leaves the existing sidecars running and is intended for an existing stack. For registry deployments, select a verified corrected immutable tag or digest. A merge to `dev` publishes `:dev`; it does not update `:latest`, which is published from `main`.
|
||||
|
||||
Check the standard image as its configured app account, using the image's Python interpreter and the same user-bin PATH prefix as the entrypoint:
|
||||
|
||||
```sh
|
||||
docker compose exec -T --user 0 odysseus sh -ec '
|
||||
gosu_bin=$(command -v gosu)
|
||||
app_user=$(getent passwd "${PUID:-1000}" | cut -d: -f1)
|
||||
test -n "$app_user"
|
||||
exec "$gosu_bin" "$app_user" env PATH="/app/.local/bin:$PATH" \
|
||||
/usr/local/bin/python /app/scripts/check_ca_trust.py \
|
||||
--system --python --grpc --overrides --verbose
|
||||
'
|
||||
```
|
||||
|
||||
`gosu` selects the account's HOME and drops privileges before Python runs, so the check includes its persisted user packages. The entrypoint itself is not rerun. Plain `docker compose exec ... python` uses the image's default root user and can miss `/app/.local` packages. A custom service user, entrypoint, virtual environment or launch command requires matching that deployment's actual user, HOME and Python interpreter instead of copying the standard command unchanged. Docker exec inherits container-creation environment, not variables exported later by the app or entrypoint: reproduce any additional Python/TLS settings with exec's `--env` options, and pass CA paths configured only in application files explicitly. Confirm the verbose identity and selected paths before accepting the result.
|
||||
|
||||
For containers started non-root or without the SETUID/SETGID capabilities needed by `gosu`, use `docker compose exec --user UID:GID --env HOME=/actual/app/home` with the deployment's actual numeric IDs, home, PATH and interpreter instead. Do not add capabilities merely to run this check.
|
||||
|
||||
SearXNG is a separate HTTPS client and image; updating the Odysseus image does not patch it. Inspect its effective CA path and update or rebuild that image independently. Also inspect persisted `/app/.local` packages, remote Cookbook environments, and externally configured MCP runtimes. Image replacement does not necessarily replace those environments.
|
||||
|
||||
For behavioral validation, all fourteen [published test chains](https://emudhra.we-have-a-problem.com/chains.zip), including the AAA cross-signs and two chains without Certificate Transparency entries, must fail certificate verification through the effective stores. Confirm that an ordinary known-good HTTPS request still succeeds. In Chromium, confirm the managed policy is loaded at `chrome://policy` and test the incident endpoints through the actual browser path with certificate verification enabled. The PEM checker cannot inspect Chromium's policy or its active verifier. Package versions and a successful image build alone do not prove that a running deployment selects the corrected store.
|
||||
|
||||
## Follow-up releases
|
||||
|
||||
The maintained bootstrap selects certifi 2026.10.23 with its verified wheel hash; pip remains 26.2.2. The certifi update removes the C1/C3 roots in addition to the earlier incident changes. CI uses uv 0.13.5, which adds verifier-level rejection of the compromised intermediate keys and updated managed CPython builds. Existing virtual environments do not update automatically. See the [certifi change](https://github.com/certifi/python-certifi/commit/b60466c9f2882d9cf088943c650b0faac06c080c), [uv release](https://github.com/astral-sh/uv/releases/tag/0.13.5), and [Python security releases](https://blog.python.org/2026/10/python-3151-3149-31317/). The pinned bootstrap still replaces the selected versions exactly; preserving newer installed versions and private-mirror support remain separate maintenance.
|
||||
|
||||
SearXNG remains an independent trust owner. Use the [upstream-image validation and rollout instructions](searxng-trust-image.md); updating these application packages does not repair an existing sidecar. Host daemons, native browser policy, optional compiled clients and independently managed integrations require their own effective-client checks.
|
||||
|
||||
CPython's [intermediate-key verifier block](https://github.com/python/cpython/pull/160042) and its [3.14 backport](https://github.com/python/cpython/pull/160569) merged on October 24. They are distinct from Python 3.14.9's bundled-pip update. No released 3.14 interpreter containing that verifier change was identified when this image pin was selected; retain the corrected effective trust stores and track the subsequent security release. hf-xet's [trust fix](https://github.com/huggingface/xet-core/pull/1038) is also merged but not included in the latest published 1.7.0 release at that check. Agent-browser 0.39.2 and primp 2.0.1 are not established incident fixes for their independent native trust paths. A version bump alone does not establish that the active client uses corrected trust.
|
||||
Reference in New Issue
Block a user