Skip to content

Merge dev into master: v1.0.0-beta.43#2087

Merged
jaylfc merged 9 commits into
masterfrom
sync/dev-to-master-beta43
Jul 21, 2026
Merged

Merge dev into master: v1.0.0-beta.43#2087
jaylfc merged 9 commits into
masterfrom
sync/dev-to-master-beta43

Conversation

@jaylfc

@jaylfc jaylfc commented Jul 21, 2026

Copy link
Copy Markdown
Owner

Promotion of dev to master for v1.0.0-beta.43. Clean merge, no conflicts.

Contents

Library app P1 (#2062): LibraryStore, ingest pipeline, cheap-tier processors (file, text, PDF, image) and the collections handoff to taOSmd over the live Collections API, with async index polling and typed link rows. Three review rounds, including a reprocess bug that deleted the user's original uploaded file and a test whose tolerance was keeping it green.

Three invite fixes, all surfaced by live use rather than tests:

Docs: Hailo-10H AI HAT+2 hardware entry with the Pi 5 M.2 slot conflict (#2075), and seven new contributor defect classes from this cycle's reviews (#2069, #2079).

Verification

Summary by CodeRabbit

  • New Features

    • Added Library ingestion for files, text, PDFs, images, and URLs, with processing status, artifacts, reprocessing, filtering, and collection indexing.
    • Invite creation now supports configurable expiration periods from 60 seconds to 24 hours.
  • Bug Fixes

    • Improved invite scope display and revocation handling for expired, redeemed, claimed, and already-revoked invites.
    • Increased the default invite expiration to one hour.
  • Documentation

    • Updated Raspberry Pi 5 and AI HAT+2 setup guidance, contributor checklists, and release documentation.

jaylfc and others added 9 commits July 20, 2026 22:18
The invite list serializers passed the raw JSON-encoded TEXT column
straight through, so clients received scopes as a string. The Projects
invite dialog calls scopes.join() expecting an array, which threw and
tripped the SPA error boundary the moment any pending invite existed.
The same crash fired on the post-mint list refresh, closing the dialog
before the URL and PIN were shown (#2065).

Parse defensively via _scopes_as_list so a malformed row degrades to []
instead of crashing every consumer.

Fixes the crash path of #2065.
…ew round (#2069)

Pitfalls: runtime state and key material never enter commits (16), new views
wire desktop AND mobile registries (17), retrofit columns on shipped stores
need guarded ALTERs because the migration runner baselines pre-existing DBs
without executing (18), scope deltas vs the issue are stated explicitly (19).
Extended pitfall 1 with the house project auth-guard pattern and pitfall 15
with the answer-in-thread requirement.

Skill: fold-first now requires in-thread replies per numbered item and bot
review freshness checks; new scope-honesty subsection covers deferral notes,
follow-up issues, and the git status runtime-artifact check.
)

The 15 minute TTL killed invites before a human could hand the link and
PIN to an agent operator: both of the maintainer's live-test invites
expired unredeemed. Default is now 1 hour, and mint accepts an optional
ttl_secs (60s to 24h) so a longer-lived invite is a deliberate choice at
mint time rather than a code change. The PIN remains the security gate;
expiry is hygiene.

Tests: default lands at 1 hour, ttl_secs is honoured, over-cap is 422.
…#2071)

* fix(invites): allow revoking expired invites, 409 for terminal states

store.revoke only matched status='pending', so an invite that lazily
flipped to expired could never be revoked: the route reported 404 while
the row kept showing in the pending list. Revoke now covers pending and
expired; the route returns 409 with the actual state for redeemed or
already-revoked invites instead of a misleading 404.

Tests: revoke-expired succeeds (store + route), revoke-redeemed refused,
double-revoke 409.

* fix(invites): name the mid-redeem state on revoke, cover 409 paths

Folds both Kilo suggestions: a 'claimed' invite is mid-redeem, so it now
returns a 409 that says so instead of the generic retry text, and both
409 route paths (redeemed and claimed) have direct tests rather than only
store-level coverage.
…ollections handoff, app UI (#2062)

* feat(library): P1 core — LibraryStore, ingest pipeline, processors, collections handoff, app UI

Implements the Library app Phase 1 from docs/design/library-app.md:

- LibraryStore (SQLite, BaseStore): items, artifacts, jobs tables
  with full CRUD and cascade deletion
- Ingest endpoint (POST /api/library/ingest): file upload + URL
  reference, async background pipeline
- Pipeline processors (cheap tier): file metadata, text extraction,
  PDF text extraction, image thumbnails + dimensions
- Collections handoff: text artifacts indexed into taosmd
- App UI (/library): drop zone, htmx item list, Pico CSS

6 API routes: GET /library, POST /api/library/ingest,
GET /api/library/items, GET/DELETE /api/library/items/{id},
POST /api/library/items/{id}/reprocess

39 tests covering: kind detection, store CRUD, all 4 processors,
pipeline runner, collections handoff, and all API routes.

Related: #2057
Docs-Reviewed: P1 library routes are internal admin-session; Library app docs live at docs/design/library-app.md. No external API surface change yet.

* fix(library): address CodeRabbit findings — missing-file error, upload cap, task GC, HTMX responses

- pipeline: mark missing source files as 'error' instead of silently 'ready'
- ingest: stream file uploads in 1MB chunks with 100MB cap, reject oversized
- background tasks: retain strong references via module-level _background_tasks set
- HTMX: return HTML fragments (.item-card) when HX-Request header is present

* fix(library): address Kilo/CodeRabbit review findings

- XSS (CRITICAL): escape title with html.escape in _render_item_card
- handed_off no longer increments on ImportError (taosmd unavailable)
- Move `import taosmd` to module level, skip loop when unavailable
- Reuse already-read bytes for taosmd ingestion (avoid double read)
- Log info for URL-only items (stored as references, not fetched)

* fix(library): folds 2-7 — drop XSS template, wire real collections contract, fix url items, idempotent reprocess, auth tests, provenance

Fold 2: Rewrite handoff_to_collections to use qmd HTTP API
  - POST /collections -> POST /collections/{id}/index contract
  - No fabricated per-item agent identities
  - No self-invented manifest.json
  - qmd_base_url passed from app.state.qmd_client; graceful skip when absent

Fold 3: Verified at 102ceae — ImportError no longer counts handed_off

Fold 4: URL-only items get reference artifact instead of empty-ready
  - Reprocess now deletes old artifacts first (idempotent per spec §2)

Fold 5: Negative auth tests (401 on all 6 endpoints), taosmd-unreachable test
  - Existing test_handoff rewritten: asserts count==0 when qmd missing

Fold 6: DROP templates/library.html + _is_htmx/_render_*/HTMLResponse
  - /library route removed; Library UI belongs to the React desktop app
  - XSS vector deleted in one move

Fold 7: data/library/ and data/collections/ added to .gitignore

Also: provenance fields (source_url, processed_at, processor) on artifacts
      test_library_page now asserts 404 (page deleted)
      test_handoff_with_qmd mocks httpx for real-contract coverage
      41/41 library tests pass

* fix(library): address Kilo findings — idempotent collection creation, normalise qmd base URL

- Look up existing collection_id from item metadata before creating a new one
  (prevents duplicate collections on reprocess)
- Strip trailing slash from qmd_base_url to avoid double-slash in API paths
- Persist collection_id in item.meta_json after first creation

* fix(library): address Kilo/CodeRabbit findings — index idempotency, verify-GET logging, concurrent reprocess guard

- Pass stable 'key' to POST /collections/{id}/index so qmd can upsert
  rather than append on reprocess (idempotent indexing)
- Log warning when verify-GET fails instead of silently swallowing
  (avoids falling through to duplicate collection creation)
- Reject POST /items/{id}/reprocess with 409 when item is already
  pending or processing (prevents concurrent pipeline races)
- Add test_reprocess_while_processing_returns_409 (42/42 pass)

* refactor(library): rewire collections handoff to live taosmd 0.4.0 API

Replace invented qmd API shapes with the live taosmd 0.4.0 collections contract:

- POST /collections: {name, kind: mixed, source_path} → resp['collection']['id']
- POST /collections/{id}/index: no body, 202 async → poll until ready|error
- POST /collections/{id}/link: {type: taos, id: item_id}
- Bearer auth from SecretsStore (taosmd-admin-token)
- Config key memory_url (taosmd :7900) not qmd.url (:7832)
- File perms: 0o640 files, 0o750 dirs
- Stats-driven return (files_indexed) not per-artifact loop

Updated caller in routes/library.py to fetch taosmd URL from config and
admin token from SecretsStore. Updated test mocks for real response shapes.

PR #2062, FOLD 1.

* fix(library): assert taosmd request sequence in handoff test

Per CodeRabbit: verify the full create → index → poll → link sequence
with call_count and URL assertions, not just the returned count.

* fix(library): fix poll nesting bug, TOCTOU CAS, log handoff return, chmod setgid

- Unwrap poll_data[collection] same as line 202 for create response
  (GET /collections/{id} returns nested shape in taosmd 0.4.0)
- Use 0o2750 (setgid) chmod for directories and files
- Log handoff return value instead of discarding it
- Log ImportError at warning, not debug
- Record collection_retryable marker on ALL handoff failure modes
- TOCTOU CAS: atomic UPDATE with rowcount check for reprocess guard
- Replace bare except OSError:pass with logged warnings in delete_item
- Remove unused LibraryStore import in library_collections.py
- Add issue link #2063 for URL-fetch deferral
- Fix test mocks to nested taosmd 0.4.0 shape
- Add POST body assertions (name/kind/source_path) in handoff test
- Assert artifact counts in test_reprocess_idempotent, not just 202s

* fix(library): address jaylfc review — data-loss guard, retryable, vacuum test, stale meta, setgid, CAS rollback, verify-GET

Findings 1-8 from PR #2062 follow-up review:

1. DATA-LOSS: Guard reprocess unlink loop to skip storage_path artifacts.
   Stop recording source file as artifact path in File/Pdf/ImageProcessors.

2. VACUOUS TEST: Fix test_reprocess_idempotent to assert exact count match
   and item status is ready (was abs() check that passed with zero artifacts).

3. Stale meta_json: Move item_meta parse before httpx try block; use item_meta
   in exception handlers instead of re-reading stale item dict.

4. retryable marker: Set collection_retryable on ALL failure paths (create
   >=400, no id, index !=202, index exception, poll status>=400, poll error,
   poll exception, poll timeout). Clear on successful handoff.

5. Issue citation: Replace #2063 (merged PR) with TODO comment.

6. setgid: Fix file perms from 0o2640 (meaningless on Linux) to 0o640.
   Keep 0o2750 on directory.

7. CAS rollback: Wrap deletion + scheduling in try/except; revert pending
   to ready on failure so item isn't stuck forever.

8. verify-GET: Distinguish 404 (fall through to create) from transient
   failures (set retryable, return 0 — no duplicate collection).

* chore(library): add issue #2078 reference, document collection_retryable as scaffolding

---------

Co-authored-by: Hogne <227774406+hognek@users.noreply.github.com>
… cycle (#2079)

* docs(skill+pitfalls): three defect classes from the Library P1 review cycle

Pitfalls: tolerances do not test invariants (20), shell snippets in template
literals must escape the interpolation sigil (21), conflict resolution reverts
fixes when done mechanically (22). All three are drawn from real defects in
this cycle, including a data-loss bug that a tolerance-based assertion kept
green and 225 syntax errors from one unescaped construct.

Skill: a verify-before-you-claim section covering tolerance assertions, mocks
built from the caller's assumptions rather than the service contract, and
compiling before opening a PR.

* docs(skill+pitfalls): external-contract mocks need a captured source

Strengthens the mock guidance from a general principle into an enforceable
rule. Scopes the danger to mocks of services we do not control, requires a
captured real response over a hand-composed fixture, asks for one feature
detecting integration test per contract, and rejects a follow-up issue as
sufficient mitigation since it detaches the caveat from the code.

* docs(skill+pitfalls): split contract mocks by whether the owner is reachable

Sibling services are not third parties. taOSmd's maintainer is on the A2A bus,
so a contract question costs one message. Every mock failure in the 2062 cycle
was a guess at something that was free to ask, and asking produced documented
envelopes, a self-corrected stats key list, and a capabilities endpoint. Guessing
is only justified when there is genuinely nobody to ask.

* docs(skill): how an external contributor reaches another team's agent

Adds the routing rule that makes pitfall 23 actionable for contributors who are
not on the A2A bus: a contract question goes to the commons repo (or taosmd
directly, which is public), the lead relays to the owning agent, and the
contributor escalates on the PR if it stalls. Marked explicitly as temporary
scaffolding that retires when contributors can hold a taOS identity.
Library P1 (ingest pipeline, cheap-tier processors, collections handoff),
three invite fixes from live testing (the dialog crash, unrevokable expired
invites, and a 15 minute TTL that killed invites before they could be used),
the Hailo AI HAT+2 hardware note, and seven new contributor defect classes.
@qodo-code-review

Copy link
Copy Markdown

Qodo reviews are paused for this user.

Troubleshooting steps vary by plan Learn more →

On a Teams plan?
Reviews resume once this user has a paid seat and their Git account is linked in Qodo.
Link Git account →

Using GitHub Enterprise Server, GitLab Self-Managed, or Bitbucket Data Center?
These require an Enterprise plan - Contact us
Contact us →

@github-actions

Copy link
Copy Markdown

👋 Thanks for the PR! This one targets master, which is our
stable branch (it's what live installs track). Please retarget it to
dev — click Edit next to the PR title and change the base
branch dropdown from master to dev. Your commits and any review
carry over, nothing is lost.

See CONTRIBUTING.md for the branch model.

@coderabbitai

coderabbitai Bot commented Jul 21, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Adds a Library app with SQLite persistence, asynchronous processing, taosmd collection handoff, and protected API routes. Extends invite TTL and revocation handling, normalizes invite scopes, updates release metadata, and expands contributor and hardware documentation.

Changes

Library foundation

Layer / File(s) Summary
Library storage and processing foundation
tinyagentos/library_store.py, tinyagentos/library_pipeline.py, tests/test_library.py
Adds item, artifact, and job persistence; kind detection; file, text, PDF, and image processors; pipeline status handling; and comprehensive storage and processor tests.
Collections handoff and indexing
tinyagentos/library_collections.py, tests/test_library.py
Copies text artifacts into per-item directories, creates or reuses taosmd collections, polls indexing readiness, links collections, tracks retryable failures, and tests unavailable and successful service flows.
Library API integration and release wiring
tinyagentos/routes/library.py, tinyagentos/routes/__init__.py, .gitignore, tests/test_library.py
Registers protected ingest, listing, retrieval, deletion, and reprocessing routes with background execution, cleanup, concurrency protection, and ignored runtime directories.

Invite lifecycle

Layer / File(s) Summary
Invite TTL and revocation lifecycle
tinyagentos/projects/invite_store.py, tinyagentos/routes/project_invites.py, tests/projects/test_invite_store.py, tests/test_routes_project_invites.py
Adds bounded per-invite TTLs, raises the default expiry to one hour, allows expired invites to be revoked, normalizes scope output, and returns state-specific conflict responses with corresponding tests.

Release guidance

Layer / File(s) Summary
Release metadata and contributor guidance
CHANGELOG.md, pyproject.toml, desktop/package.json, tinyagentos/__init__.py, docs/*, .claude/skills/taos-development-skill/SKILL.md
Updates the beta version and changelog, documents Raspberry Pi accelerator storage constraints, and expands contributor workflow, testing, scope, conflict, and runtime-artifact guidance.

Estimated code review effort: 4 (Complex) | ~60 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Client
  participant LibraryRoutes
  participant Pipeline
  participant LibraryStore
  participant taosmd
  Client->>LibraryRoutes: Upload file or submit URL
  LibraryRoutes->>LibraryStore: Create pending item
  LibraryRoutes->>Pipeline: Schedule background processing
  Pipeline->>LibraryStore: Store artifacts and ready status
  Pipeline->>taosmd: Create or reuse collection
  taosmd-->>Pipeline: Collection ready with indexed count
  Pipeline->>taosmd: Link collection to library item
Loading

Possibly related PRs

  • jaylfc/taOS#2062: Overlaps directly with the Library store, pipeline, collection handoff, routes, and tests.
  • jaylfc/taOS#2071: Overlaps with invite revocation state handling.
  • jaylfc/taOS#2072: Overlaps with invite TTL configuration and minting behavior.

Suggested reviewers: hognek

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 51.02% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the release merge and version bump to v1.0.0-beta.43.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch sync/dev-to-master-beta43

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@gitar-bot

gitar-bot Bot commented Jul 21, 2026

Copy link
Copy Markdown

Important

You are using the Gitar free plan. Upgrade to unlock code review, CI analysis, auto-apply, custom automations, and more.

Gitar

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 8

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@CHANGELOG.md`:
- Around line 40-44: Update the contributor documentation changelog entry to say
“eight” instead of “seven,” leaving the listed defect classes unchanged.

In `@docs/contributor-pitfalls.md`:
- Around line 212-214: Update the external-response fixture guidance in
docs/contributor-pitfalls.md lines 212-214 and
.claude/skills/taos-development-skill/SKILL.md lines 312-315 to prohibit
committing raw responses and require minimal fixtures captured from safe test
accounts with secrets, tokens, cookies, PII, and restricted fields removed. Add
this sanitization requirement before the instruction to commit the service
response as a fixture in both sites.

In `@docs/getting-started.md`:
- Around line 34-35: The AI HAT+2 description currently misidentifies the shared
resource as the Raspberry Pi 5’s M.2 slot. Update docs/getting-started.md lines
34-35 and CHANGELOG.md lines 37-39 to state that the AI HAT+2 uses the Pi 5’s
PCIe interface, which is also used by the M.2 HAT+ for NVMe, while preserving
the recommendation to use USB storage alongside the accelerator.

In `@tests/projects/test_invite_store.py`:
- Around line 342-353: Update both tests/projects/test_invite_store.py:342-353
and tests/test_routes_project_invites.py:658-668 to call mark_redeemed() after
redeem() and before the existing assertions, ensuring they exercise the redeemed
state rather than only the claimed state.

In `@tinyagentos/library_pipeline.py`:
- Around line 260-265: Declare pypdf as a runtime dependency in pyproject.toml
so PDF extraction is available on fresh installs. In
tinyagentos/library_pipeline.py lines 209-211, preserve the existing PDF
import/extraction behavior while ensuring the optional ImportError no longer
occurs; the ImageProcessor class at lines 260-265 requires no direct change.

In `@tinyagentos/projects/invite_store.py`:
- Around line 184-186: Update the TTL validation in mint() to reject values
below 60 seconds or above 86400 seconds, while continuing to allow None to use
_EXPIRY_SECS. Perform this validation before calculating expires_ts or
persisting the invite, and retain the existing ValueError behavior for invalid
TTLs.

In `@tinyagentos/routes/library.py`:
- Around line 230-241: Update the list_items endpoint to validate and clamp
limit and offset before passing them to store.list_items: enforce non-negative
offset, enforce a positive bounded limit, and cap oversized values at the
endpoint’s maximum page size. Keep the existing kind/status filters and response
shape unchanged.
- Around line 64-75: Update create_app and _get_library_store so library_store
is initialized during application setup and assigned to app.state.library_store
before requests are served. Remove the lazy unsynchronized check-and-set path in
_get_library_store, while preserving retrieval of the initialized store for
route handlers.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 9b227e47-1c5b-416c-897e-f0ff18e1a204

📥 Commits

Reviewing files that changed from the base of the PR and between b736cdc and 61a0769.

⛔ Files ignored due to path filters (1)
  • uv.lock is excluded by !**/*.lock
📒 Files selected for processing (18)
  • .claude/skills/taos-development-skill/SKILL.md
  • .gitignore
  • CHANGELOG.md
  • desktop/package.json
  • docs/contributor-pitfalls.md
  • docs/getting-started.md
  • pyproject.toml
  • tests/projects/test_invite_store.py
  • tests/test_library.py
  • tests/test_routes_project_invites.py
  • tinyagentos/__init__.py
  • tinyagentos/library_collections.py
  • tinyagentos/library_pipeline.py
  • tinyagentos/library_store.py
  • tinyagentos/projects/invite_store.py
  • tinyagentos/routes/__init__.py
  • tinyagentos/routes/library.py
  • tinyagentos/routes/project_invites.py

Comment thread CHANGELOG.md
Comment on lines +40 to +44
- Contributor docs gained seven new defect classes drawn from real review
findings, covering runtime state in commits, mobile view registries, retrofit
migrations on shipped stores, scope honesty, tolerance assertions, shell
snippets in template literals, conflict resolution, and how an external
contributor reaches another team's agent (#2069, #2079).

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Fix the defect-class count.

This says “seven” but lists eight classes: runtime state, mobile registries, migrations, scope honesty, tolerance assertions, shell snippets, conflict resolution, and external-agent coordination.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@CHANGELOG.md` around lines 40 - 44, Update the contributor documentation
changelog entry to say “eight” instead of “seven,” leaving the listed defect
classes unchanged.

Comment on lines +212 to +214
**Genuinely third-party (GitHub, OpenRouter, Reddit): capture, do not compose.**
There is no one to ask, so call the real service once and commit its response as
the fixture. A recorded response cannot encode a wrong belief.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

Require sanitized fixtures for captured external responses.

Both rules should prohibit raw response commits and require minimal fixtures from safe test accounts with secrets, tokens, cookies, PII, and restricted fields removed.

  • docs/contributor-pitfalls.md#L212-L214: add the sanitization requirement before “commit its response as the fixture.”
  • .claude/skills/taos-development-skill/SKILL.md#L312-L315: apply the same requirement to the external-service testing workflow.
📍 Affects 2 files
  • docs/contributor-pitfalls.md#L212-L214 (this comment)
  • .claude/skills/taos-development-skill/SKILL.md#L312-L315
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/contributor-pitfalls.md` around lines 212 - 214, Update the
external-response fixture guidance in docs/contributor-pitfalls.md lines 212-214
and .claude/skills/taos-development-skill/SKILL.md lines 312-315 to prohibit
committing raw responses and require minimal fixtures captured from safe test
accounts with secrets, tokens, cookies, PII, and restricted fields removed. Add
this sanitization requirement before the instruction to commit the service
response as a fixture in both sites.

Comment thread docs/getting-started.md
Comment on lines +34 to +35
> **AI HAT+2 (Hailo-10H) and the Pi 5 M.2 slot:** The AI HAT+2 NPU accelerator occupies the Raspberry Pi 5's only M.2 slot, the same slot normally used for an NVMe boot drive. You cannot use the HAT and an NVMe SSD at the same time. If you want fast storage alongside Hailo LLM acceleration, use a USB SSD instead.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Use PCIe terminology consistently for AI HAT+2.

The AI HAT+2 connects through Raspberry Pi 5’s PCIe port; the M.2 HAT+ provides the M.2 connector for NVMe and uses that same PCIe interface. Update both descriptions while preserving the valid recommendation to use USB storage alongside the accelerator. (raspberrypi.com)

  • docs/getting-started.md#L34-L35: replace “occupies the Raspberry Pi 5’s only M.2 slot” with the PCIe-sharing explanation.
  • CHANGELOG.md#L37-L39: use the same corrected hardware terminology in the release notes.
📍 Affects 2 files
  • docs/getting-started.md#L34-L35 (this comment)
  • CHANGELOG.md#L37-L39
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/getting-started.md` around lines 34 - 35, The AI HAT+2 description
currently misidentifies the shared resource as the Raspberry Pi 5’s M.2 slot.
Update docs/getting-started.md lines 34-35 and CHANGELOG.md lines 37-39 to state
that the AI HAT+2 uses the Pi 5’s PCIe interface, which is also used by the M.2
HAT+ for NVMe, while preserving the recommendation to use USB storage alongside
the accelerator.

Source: MCP tools

Comment on lines +342 to +353
async def test_revoke_redeemed_invite_returns_false(store):
result = await store.mint(
project_id="prj-1",
scopes=[],
approval_mode="auto",
check_interval_secs=1800,
created_by="u",
)
iid = result["record"]["invite_id"]
pin = result["pin"]
await store.redeem(iid, pin)
assert await store.revoke(iid) is False

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Exercise the redeemed state, not only claimed.

redeem() leaves the invite claimed; both tests therefore miss the actual redeemed-state behavior.

  • tests/projects/test_invite_store.py#L342-L353: call mark_redeemed() after redeem() before asserting revoke() returns False.
  • tests/test_routes_project_invites.py#L658-L668: call mark_redeemed() after redeem() so the route’s status == "redeemed" branch is covered.
📍 Affects 2 files
  • tests/projects/test_invite_store.py#L342-L353 (this comment)
  • tests/test_routes_project_invites.py#L658-L668
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/projects/test_invite_store.py` around lines 342 - 353, Update both
tests/projects/test_invite_store.py:342-353 and
tests/test_routes_project_invites.py:658-668 to call mark_redeemed() after
redeem() and before the existing assertions, ensuring they exercise the redeemed
state rather than only the claimed state.

Comment on lines +260 to +265
class ImageProcessor(Processor):
"""Image processor — records dimensions, creates thumbnail.

Thumbnail generation requires Pillow (PIL), which is always available in
the taOS dev dependencies.
"""

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check whether pypdf and Pillow are declared as runtime (not dev-only) dependencies
fd -t f 'pyproject.toml|requirements*.txt|setup.cfg|setup.py' --exec sh -c \
  'echo "== $1 =="; rg -niP "pillow|pypdf|pypdf2" "$1" || echo "  (no match)"' _ {}

Repository: jaylfc/taOS

Length of output: 340


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== dependency declarations =="
sed -n '1,120p' pyproject.toml

echo
echo "== pypdf / Pillow references in repo =="
rg -n -i 'pypdf|pillow|pypdf2' .

echo
echo "== library_pipeline.py relevant sections =="
sed -n '180,290p' tinyagentos/library_pipeline.py

Repository: jaylfc/taOS

Length of output: 34439


Declare pypdf as a runtime dependency tinyagentos/library_pipeline.py#L209-L211 swallows ImportError, so PDF text extraction silently no-ops on fresh installs unless pypdf is added to pyproject.toml.

📍 Affects 1 file
  • tinyagentos/library_pipeline.py#L260-L265 (this comment)
  • tinyagentos/library_pipeline.py#L209-L211
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tinyagentos/library_pipeline.py` around lines 260 - 265, Declare pypdf as a
runtime dependency in pyproject.toml so PDF extraction is available on fresh
installs. In tinyagentos/library_pipeline.py lines 209-211, preserve the
existing PDF import/extraction behavior while ensuring the optional ImportError
no longer occurs; the ImageProcessor class at lines 260-265 requires no direct
change.

Comment on lines +184 to +186
if ttl_secs is not None and ttl_secs <= 0:
raise ValueError("ttl_secs must be positive")
expires_ts = now + (ttl_secs if ttl_secs is not None else _EXPIRY_SECS)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 Security & Privacy | 🟡 Minor | ⚡ Quick win

Enforce the TTL bounds in the store as well.

mint() accepts any positive value, bypassing the route’s 60–86400 second contract for direct callers. Validate both bounds here before persisting the expiry.

Proposed fix
-        if ttl_secs is not None and ttl_secs <= 0:
-            raise ValueError("ttl_secs must be positive")
+        if ttl_secs is not None and not 60 <= ttl_secs <= 86_400:
+            raise ValueError("ttl_secs must be between 60 and 86400")
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if ttl_secs is not None and ttl_secs <= 0:
raise ValueError("ttl_secs must be positive")
expires_ts = now + (ttl_secs if ttl_secs is not None else _EXPIRY_SECS)
if ttl_secs is not None and not 60 <= ttl_secs <= 86_400:
raise ValueError("ttl_secs must be between 60 and 86400")
expires_ts = now + (ttl_secs if ttl_secs is not None else _EXPIRY_SECS)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tinyagentos/projects/invite_store.py` around lines 184 - 186, Update the TTL
validation in mint() to reject values below 60 seconds or above 86400 seconds,
while continuing to allow None to use _EXPIRY_SECS. Perform this validation
before calculating expires_ts or persisting the invite, and retain the existing
ValueError behavior for invalid TTLs.

Comment on lines +64 to +75
async def _get_library_store(request: Request):
"""Get the LibraryStore from app.state (lazily initialised)."""
store = getattr(request.app.state, "library_store", None)
if store is None:
from tinyagentos.library_store import LibraryStore

data_dir = getattr(request.app.state, "data_dir", None)
base = Path(data_dir) if data_dir else Path(__file__).parent.parent.parent / "data"
store = LibraryStore(base / "library.db")
await store.init()
request.app.state.library_store = store
return store

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify whether library_store is created at app startup (vs. only lazily here)
rg -nP 'library_store' -g '!tests/**' -C2

Repository: jaylfc/taOS

Length of output: 149


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n== Files mentioning create_app or app.state ==\n'
rg -n "create_app|app\.state|library_store|LibraryStore" tinyagentos -g '!tests/**' || true

printf '\n== Top-level file tree around app/routes ==\n'
git ls-files tinyagentos | sed -n '1,200p'

printf '\n== library.py outline ==\n'
ast-grep outline tinyagentos/routes/library.py --view expanded || true

Repository: jaylfc/taOS

Length of output: 50368


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n== app.py around library_store startup ==\n'
sed -n '1540,1675p' tinyagentos/app.py | cat -n

printf '\n== app.py around shutdown cleanup ==\n'
sed -n '1468,1495p' tinyagentos/app.py | cat -n

printf '\n== routes/library.py around helper ==\n'
sed -n '1,90p' tinyagentos/routes/library.py | cat -n

Repository: jaylfc/taOS

Length of output: 12817


Initialize library_store before serving requests_get_library_store still does an unsynchronized check-then-set, and create_app doesn’t assign app.state.library_store, so concurrent first hits can build duplicate SQLite-backed stores. tinyagentos/routes/library.py:64-75

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tinyagentos/routes/library.py` around lines 64 - 75, Update create_app and
_get_library_store so library_store is initialized during application setup and
assigned to app.state.library_store before requests are served. Remove the lazy
unsynchronized check-and-set path in _get_library_store, while preserving
retrieval of the initialized store for route handlers.

Comment on lines +230 to +241
@router.get("/api/library/items")
async def list_items(
request: Request,
kind: str | None = None,
status: str | None = None,
limit: int = 50,
offset: int = 0,
):
"""List library items, optionally filtered by kind or status."""
store = await _get_library_store(request)
items = await store.list_items(kind=kind, status=status, limit=limit, offset=offset)
return {"items": items, "count": len(items)}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Clamp limit/offset before querying.

limit and offset are forwarded unvalidated to the SQL LIMIT ? OFFSET ?. A negative limit maps to LIMIT -1 in SQLite, which returns all rows (unbounded), and there is no upper bound to prevent oversized page requests.

🛡️ Suggested clamp
-    store = await _get_library_store(request)
-    items = await store.list_items(kind=kind, status=status, limit=limit, offset=offset)
+    store = await _get_library_store(request)
+    limit = max(1, min(limit, 200))
+    offset = max(0, offset)
+    items = await store.list_items(kind=kind, status=status, limit=limit, offset=offset)
     return {"items": items, "count": len(items)}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
@router.get("/api/library/items")
async def list_items(
request: Request,
kind: str | None = None,
status: str | None = None,
limit: int = 50,
offset: int = 0,
):
"""List library items, optionally filtered by kind or status."""
store = await _get_library_store(request)
items = await store.list_items(kind=kind, status=status, limit=limit, offset=offset)
return {"items": items, "count": len(items)}
`@router.get`("/api/library/items")
async def list_items(
request: Request,
kind: str | None = None,
status: str | None = None,
limit: int = 50,
offset: int = 0,
):
"""List library items, optionally filtered by kind or status."""
store = await _get_library_store(request)
limit = max(1, min(limit, 200))
offset = max(0, offset)
items = await store.list_items(kind=kind, status=status, limit=limit, offset=offset)
return {"items": items, "count": len(items)}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tinyagentos/routes/library.py` around lines 230 - 241, Update the list_items
endpoint to validate and clamp limit and offset before passing them to
store.list_items: enforce non-negative offset, enforce a positive bounded limit,
and cap oversized values at the endpoint’s maximum page size. Keep the existing
kind/status filters and response shape unchanged.

@jaylfc
jaylfc merged commit 7e4ca0e into master Jul 21, 2026
11 of 12 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants