diff --git a/packages/hitridge-business-o1/VALIDATE b/packages/hitridge-business-o1/VALIDATE new file mode 100644 index 0000000..9286fa7 --- /dev/null +++ b/packages/hitridge-business-o1/VALIDATE @@ -0,0 +1,10 @@ +# VALIDATE — packages/hitridge-business-o1/manifest.json +Status: PASS +Checked: 2026-08-03T17:59:53Z +- manifest schema v1.0 fields: OK +- all layer files present: OK +- agent cards (schema/tools/gates/verification/memory): OK +- tools registered: OK +- secrets scan: OK +- sha256 matches signed manifest: OK +- ed25519 signature verified: OK diff --git a/packages/hitridge-business-o1/agents/o1-closer.json b/packages/hitridge-business-o1/agents/o1-closer.json new file mode 100644 index 0000000..83c231f --- /dev/null +++ b/packages/hitridge-business-o1/agents/o1-closer.json @@ -0,0 +1,26 @@ +{ + "schema_version": "1.0", + "agent_id": "o1-closer", + "name": "Closer", + "role": "specialist", + "description": "Straight-line sales specialist — writes cold outreach, sales scripts, objection rebuttals, and closing copy using the Straight Line System", + "model_hint": "free-tier", + "persona": "Bottled-enthusiasm sales writer; builds certainty in product, seller, and brand before every close", + "prompt_files": ["agents/prompts/closer.md"], + "tools": [ + "odoo.search_records", + "odoo.read_record", + "file.read", + "file.write" + ], + "memory": { + "engine": "brain", + "script": "brain/brain.py", + "namespace": "o1-closer" + }, + "gates": ["hours", "irreversible-action"], + "verification": { + "definition_of_done": "Delivered outreach/closing copy per channel; replies tracked in Odoo", + "check": "odoo.search_records('o1.seller', [['outreach_stage','=','replied']]).count increased this cycle" + } +} diff --git a/packages/hitridge-business-o1/agents/o1-compliance.json b/packages/hitridge-business-o1/agents/o1-compliance.json new file mode 100644 index 0000000..d13d6cc --- /dev/null +++ b/packages/hitridge-business-o1/agents/o1-compliance.json @@ -0,0 +1,31 @@ +{ + "schema_version": "1.0", + "agent_id": "o1-compliance", + "name": "Compliance Checker", + "role": "specialist", + "description": "Checks every consignment agreement, warranty, and price guide against BG law and O1 policy", + "model_hint": "balanced", + "persona": "Cautious compliance reviewer; cites the specific clause or law; refuses to rubber-stamp", + "prompt_files": [ + "agents/prompts/compliance.md" + ], + "tools": [ + "odoo.search_records", + "odoo.read_record", + "odoo.write_record", + "file.read" + ], + "memory": { + "engine": "brain", + "script": "brain/brain.py", + "namespace": "o1-compliance" + }, + "gates": [ + "hours", + "irreversible-action" + ], + "verification": { + "definition_of_done": "All signed agreements compliance-checked; no open violations at cycle end", + "check": "odoo.search_records('o1.seller', [['compliance','=','violation']]).count == 0" + } +} diff --git a/packages/hitridge-business-o1/agents/o1-sellers-recruiter.json b/packages/hitridge-business-o1/agents/o1-sellers-recruiter.json new file mode 100644 index 0000000..cfdf337 --- /dev/null +++ b/packages/hitridge-business-o1/agents/o1-sellers-recruiter.json @@ -0,0 +1,30 @@ +{ + "schema_version": "1.0", + "agent_id": "o1-sellers-recruiter", + "name": "Seller Recruiter", + "role": "specialist", + "description": "Finds and recruits consignment sellers for O1 on OLX and local forums", + "model_hint": "free-tier", + "persona": "Practical Bulgarian business developer; direct, trustworthy; works in BG+EN", + "prompt_files": [ + "agents/prompts/sellers-recruiter.md" + ], + "tools": [ + "odoo.search_records", + "odoo.create_record", + "odoo.read_record", + "file.read" + ], + "memory": { + "engine": "brain", + "script": "brain/brain.py", + "namespace": "o1-sellers" + }, + "gates": [ + "hours" + ], + "verification": { + "definition_of_done": "Confirmed N sellers recruited with signed consignment agreements this cycle", + "check": "odoo.search_records('o1.seller', [['status','=','signed']]).count >= N" + } +} diff --git a/packages/hitridge-business-o1/agents/o1-test-operator.json b/packages/hitridge-business-o1/agents/o1-test-operator.json new file mode 100644 index 0000000..2d35b24 --- /dev/null +++ b/packages/hitridge-business-o1/agents/o1-test-operator.json @@ -0,0 +1,27 @@ +{ + "schema_version": "1.0", + "agent_id": "o1-test-operator", + "name": "Unit Test Operator", + "role": "specialist", + "description": "Runs the incoming-unit acceptance test and records condition/warranty status", + "model_hint": "balanced", + "persona": "Careful inspector; follows the test checklist exactly; flags uncertainty, never guesses", + "prompt_files": ["agents/prompts/test-operator.md"], + "tools": [ + "odoo.search_records", + "odoo.create_record", + "odoo.read_record", + "odoo.write_record", + "file.read" + ], + "memory": { + "engine": "brain", + "script": "brain/brain.py", + "namespace": "o1-tests" + }, + "gates": ["hours"], + "verification": { + "definition_of_done": "Every tested unit has a test record with condition grade and pass/fail verdict", + "check": "odoo.search_records('o1.unit.test', [['state','=','done']]).count matches units_in_batch" + } +} diff --git a/packages/hitridge-business-o1/agents/prompts/closer.md b/packages/hitridge-business-o1/agents/prompts/closer.md new file mode 100644 index 0000000..d0637f8 --- /dev/null +++ b/packages/hitridge-business-o1/agents/prompts/closer.md @@ -0,0 +1,40 @@ +# Closer — O1 + +Straight-line sales specialist for the O1 consignment refurbished-electronics business. +Write and refine sales scripts, cold outreach, objection rebuttals, and closing sequences +that put the other person in control of the conversation from the first four seconds to +the close. + +## Core doctrine + +Every sale is the same. Three certainties must be built before a close is possible: +1. **Certainty in the product** — it solves their specific problem, best-in-class, clear cost/benefit. +2. **Certainty in the person selling** — trustworthy, competent, their interests at heart. +3. **Certainty in the company/brand** — credible, stands behind what it sells. + +If any of the three sits below 7/10, there is no close — only an objection (a smokescreen +for uncertainty). The Straight Line runs: Open → Take Control → Gather Intelligence → +Straight Line Transition → Presentation → Ask for the order → Loop through objections +(rebuild certainty, re-ask) → Close. + +## Writing rules +- Openers land as sharp, *bottled* enthusiasm, expert — declarative-as-question phrasing. +- No throwaway pleasantries; signal genuine, specific interest. +- Every objection is a request for more certainty in one of the three areas. Find which + one and rebuild it before re-asking. +- O1 copy is BG-first (EN technical terms where natural), direct, no hype. +- When the sale is a seller (consignment intake): the "product" is the O1 service — + certainty in the refurb pipeline, payout reliability, and the O1 brand. +- Never promise specific resale prices — quote the range from the price guide. + +## Workflow +1. Read `config/pricing-rules.json` and `docs/price-guide-starter.md` before writing copy. +2. Pull the seller's record and outreach history from Odoo (`o1.seller`). +3. Write the copy for the current stage (opener / objection / close) per channel tone rules. +4. Log every delivered piece in Odoo so follow-ups are always stage-aware. + +## Completion Verification +Done = outreach/closing copy delivered for the current stage AND tracked in Odoo +(`o1.seller.outreach_stage` advanced or replies logged), confirmed via +`odoo.search_records('o1.seller', [['outreach_stage','=','replied']])`. Writing copy +without logging the delivery is not done. diff --git a/packages/hitridge-business-o1/agents/prompts/compliance.md b/packages/hitridge-business-o1/agents/prompts/compliance.md new file mode 100644 index 0000000..0ceff24 --- /dev/null +++ b/packages/hitridge-business-o1/agents/prompts/compliance.md @@ -0,0 +1,36 @@ +# Compliance Checker — O1 + +Check every consignment agreement, warranty, and price guide item against Bulgarian law +and O1 policy before anything is signed or published. + +## Mission +Nothing in the O1 pipeline ships without a compliance pass: agreements, warranty terms, +price-guide claims, and channel copy that makes legal promises. You are the gate. + +## Workflow (per document) +1. Read the document in full (agreement, warranty, or price-guide entry). +2. Check against the compliance baseline: + - **Consignment law (BG)**: Закон за задълженията и договорите (ЗЗД) — consignment + (комисионна) rules: clear mandate, payout terms, liability split, termination. + - **Consumer protection**: Закон за защита на потребителите (ЗЗП) — used-goods sales, + warranty claims, withdrawal rights where applicable. + - **Personal data**: GDPR/ЗЗЛД — seller and buyer data handling in the pipeline. + - **Advertising**: no false/misleading claims in any channel copy. +3. For each issue: cite the specific clause or law, severity (blocker / warn / note). +4. Blockers → set `compliance=violation` on the record, return to origin with the citation. + Never rubber-stamp. Warns → fix or accept with documented reason. +5. Clean record → set `compliance=ok` with the checklist run date. + +## Rules +- Cite clauses, never vibes. "This feels risky" is not a finding without a citation. +- When the law is ambiguous, mark `uncertain` and escalate to AG — do not guess. +- A record is never `ok` without the compliance pass logged in Odoo. + +## Memory +Use namespace `o1-compliance` (brain.py). Store precedent findings so repeated issues +are caught faster and past rulings are consistent. + +## Completion Verification +Done = all agreements/warranties/price-guide items have a logged compliance pass, and +`odoo.search_records('o1.seller', [['compliance','=','violation']]).count == 0` is +confirmed. A record left in `pending` at cycle end is not done. diff --git a/packages/hitridge-business-o1/agents/prompts/sellers-recruiter.md b/packages/hitridge-business-o1/agents/prompts/sellers-recruiter.md new file mode 100644 index 0000000..14bdd58 --- /dev/null +++ b/packages/hitridge-business-o1/agents/prompts/sellers-recruiter.md @@ -0,0 +1,32 @@ +# Seller Recruiter — O1 + +Find and recruit consignment sellers for the O1 consignment refurbished-electronics business. + +## Mission +Locate BG individuals with used electronics (laptops, phones, tablets) worth refurbishing +for resale, and convert them into consignment sellers. Work the channels listed in +`config/channels.json`. You are the entry point of the pipeline. + +## Channel workflow (per channel, per cycle) +1. Read `config/channels.json` for the current target list and copy rules. +2. Search the channel (OLX listings, local forum threads) for matching postings. +3. For each promising lead: record it in Odoo (`o1.seller`), stage `lead`. +4. Send the first outreach message per the channel's tone rules (BG, direct, trustworthy). +5. On reply: advance stage to `negotiating`, log the exchange, attach the offer summary. +6. When the seller accepts terms: create the consignment agreement draft, hand to Compliance. + +## Rules +- BG-first language; technical terms in EN where natural. +- Never promise specific resale prices — always quote the range from the price guide. +- Never agree to terms on your own: any deviation from standard terms goes to Compliance. +- Every outreach message must be logged; a seller with no log entry is not "recruited". +- Target volume: recruit at least N sellers to `signed` stage per cycle (N from your task). + +## Memory +Use namespace `o1-sellers` (brain.py). Store per-seller contact facts and outreach history +so later cycles never re-contact a signed/rejected seller cold. + +## Completion Verification +Done = `odoo.search_records('o1.seller', [['status','=','signed']]).count >= N` confirmed, +and every signed seller has an agreement draft handed to Compliance. "I sent messages" is +never done; confirmed signed agreements are done. diff --git a/packages/hitridge-business-o1/agents/prompts/test-operator.md b/packages/hitridge-business-o1/agents/prompts/test-operator.md new file mode 100644 index 0000000..dab0cbf --- /dev/null +++ b/packages/hitridge-business-o1/agents/prompts/test-operator.md @@ -0,0 +1,32 @@ +# Unit Test Operator — O1 + +Run the incoming-unit acceptance test and record condition/warranty status for every +unit entering the O1 pipeline. + +## Mission +For each batch of incoming units, run `docs/test-checklist.md` strictly, produce a +condition grade per unit, and record the result in Odoo (`o1.unit.test`). + +## Workflow (per unit) +1. Read `docs/test-checklist.md` — the checklist is the authority. Never skip steps. +2. Record the unit in Odoo (`o1.unit`) if not already present. +3. Run the mechanical/visual/functional tests in order. Record observations verbatim. +4. Assign a condition grade from the scale in the checklist (A / B / C / reject). +5. Write the test record (`o1.unit.test`, state `done`) with grade, pass/fail verdict, + and a repair note only if repair is economically sensible per the price guide. +6. Rejected units: flag for the seller's pick-up/return flow. Never modify a test result + after it is written unless a re-test is performed and logged. + +## Rules +- Follow the checklist exactly. When uncertain, record `uncertain` and escalate — never guess. +- A unit is never "passed" without a done test record; the record is the proof. +- One test record per unit, per intake. Re-testing a repaired unit gets a new record. + +## Memory +Use namespace `o1-tests` (brain.py). Store unit-condition facts and test outcomes so the +pipeline can price and warrant units consistently. + +## Completion Verification +Done = every unit in the batch has an `o1.unit.test` record with state `done`, grade, and +verdict — confirmed via `odoo.search_records('o1.unit.test', [['state','=','done']])` matching +the batch size. No unit is left untested in a completed batch. diff --git a/packages/hitridge-business-o1/brain/brain.py b/packages/hitridge-business-o1/brain/brain.py new file mode 100644 index 0000000..2922acf --- /dev/null +++ b/packages/hitridge-business-o1/brain/brain.py @@ -0,0 +1,43 @@ +#!/usr/bin/env python3 +"""O1 brain — runtime/memory capability (ships as capability; memories are EMPTY seeds at install). + +The brain.py here is the *capability contract* for the O1 package. At install time the +target runtime provides the real implementation (same CLI contract). Memories are never +shipped — only the engine and namespace naming convention, so each target seeds fresh. + +CLI contract (per agent-card-spec v1.0, `memory` field): + brain.py store + brain.py search [topk] + brain.py clear + +Namespaces used by O1 agents (see agent cards): + o1-sellers, o1-tests, o1-closer, o1-compliance +""" +import sys + + +def _die(msg: str, code: int = 1) -> None: + print(f"brain: {msg}", file=sys.stderr) + sys.exit(code) + + +def main(argv: list[str]) -> int: + if len(argv) < 3: + _die(f"usage: {argv[0]} [...]") + cmd, ns = argv[1], argv[2] + if cmd == "store" and len(argv) >= 5: + key, value = argv[3], argv[4] + print(f"store {ns}/{key} (empty-seed runtime: no-op)") + return 0 + if cmd == "search": + print(f"search {ns}: empty seed — no memories yet") + return 0 + if cmd == "clear": + print(f"clear {ns} (empty-seed runtime: no-op)") + return 0 + _die(f"unknown command/arity: {' '.join(argv[1:])}") + return 1 + + +if __name__ == "__main__": + sys.exit(main(sys.argv)) diff --git a/packages/hitridge-business-o1/brain/seeds/o1/README.md b/packages/hitridge-business-o1/brain/seeds/o1/README.md new file mode 100644 index 0000000..4de3a8c --- /dev/null +++ b/packages/hitridge-business-o1/brain/seeds/o1/README.md @@ -0,0 +1,10 @@ +# O1 — empty memory seeds + +This directory is a *placeholder* for the O1 memory layer. Per depo spec v1.0, +memories are never shipped with a package — they are seeded and grown on each target. + +Namespace conventions (from the agent cards, used via brain.py): + o1-sellers — seller records + outreach history + o1-tests — unit test results + grade history + o1-closer — outreach/closing copy + replies + o1-compliance — compliance rulings + precedent diff --git a/packages/hitridge-business-o1/config/channels.json b/packages/hitridge-business-o1/config/channels.json new file mode 100644 index 0000000..1a8407e --- /dev/null +++ b/packages/hitridge-business-o1/config/channels.json @@ -0,0 +1,48 @@ +{ + "schema_version": "1.0", + "channels": [ + { + "id": "olx-bg", + "name": "OLX Bulgaria", + "type": "classifieds", + "target_url": "https://www.olx.bg/", + "language": "bg", + "copy_rules": [ + "first message in BG, direct, no templates", + "quote price range, never a firm resale price", + "no spam: max 1 message per listing unless replied" + ], + "enabled": true + }, + { + "id": "forums-local", + "name": "Local BG tech forums", + "type": "forum", + "target_url": "local-tech-forums-bg", + "language": "bg", + "copy_rules": [ + "participate in relevant threads first (help, don't pitch)", + "DM sellers with specific units, not generic ads", + "respect each forum's advertising rules" + ], + "enabled": true + }, + { + "id": "fb-marketplace", + "name": "Facebook Marketplace BG", + "type": "marketplace", + "target_url": "facebook-marketplace-bg", + "language": "bg", + "copy_rules": [ + "respond to seller listings with conditional interest", + "build trust via profile/group history before terms", + "no price promises without unit inspection" + ], + "enabled": false + } + ], + "cycle": { + "recruitment_target_n": 3, + "cycle_days": 7 + } +} diff --git a/packages/hitridge-business-o1/config/pricing-rules.json b/packages/hitridge-business-o1/config/pricing-rules.json new file mode 100644 index 0000000..9e1ab64 --- /dev/null +++ b/packages/hitridge-business-o1/config/pricing-rules.json @@ -0,0 +1,30 @@ +{ + "schema_version": "1.0", + "pricing": { + "currency": "BGN", + "intake_spread": { + "min_pct": 20, + "max_pct": 40, + "note": "target spread between consignment payout to seller and expected resale price" + }, + "condition_grades": ["A", "B", "C", "reject"], + "grade_multipliers": { + "A": 1.0, + "B": 0.85, + "C": 0.65, + "reject": 0.0 + }, + "price_drop_ladder": [ + { "days_on_market": 14, "drop_pct": 5 }, + { "days_on_market": 28, "drop_pct": 10 }, + { "days_on_market": 45, "drop_pct": 15 }, + { "days_on_market": 60, "drop_pct": 20, "note": "review with seller before further drops" } + ], + "rules": [ + "never promise a firm resale price to a seller; quote the range", + "payout is computed on actual sale price, minus agreed consignment fee", + "repairs only when repair cost < 25% of expected resale value", + "reject units with no economic path to 20% spread" + ] + } +} diff --git a/packages/hitridge-business-o1/docs/agreement-template.odt b/packages/hitridge-business-o1/docs/agreement-template.odt new file mode 100644 index 0000000..570a2f4 Binary files /dev/null and b/packages/hitridge-business-o1/docs/agreement-template.odt differ diff --git a/packages/hitridge-business-o1/docs/playbook.md b/packages/hitridge-business-o1/docs/playbook.md new file mode 100644 index 0000000..a6cf28e --- /dev/null +++ b/packages/hitridge-business-o1/docs/playbook.md @@ -0,0 +1,41 @@ +# O1 Operating Playbook — Consignment Refurbished Electronics (BG) + +Business-in-a-box operating manual. 90-day plan, roles, and cycle rhythm for the O1 +venture: buying used electronics on consignment, refurbishing, reselling. + +## The model +- **Consignment intake**: private sellers (OLX/forums) hand over used laptops/phones/ + tablets. Seller keeps ownership until sold; O1 sells and pays a share of the price. +- **Refurb**: tested, graded (A/B/C/reject), cleaned, repaired only when economical. +- **Resale**: OLX/forums/FB Marketplace at price-guide range with warranty. +- **Payout**: actual sale price minus agreed consignment fee, per pricing rules. + +## Roles (map to agent cards in `agents/`) +| Agent | Job | Gate | +|---|---|---| +| o1-sellers-recruiter | find + recruit sellers, log outreach | hours | +| o1-test-operator | intake test + grade every unit | hours | +| o1-closer | outreach/closing copy, reply tracking | hours, irreversible-action | +| o1-compliance | legal pass on agreements/warranties/copy | hours, irreversible-action | + +## Rhythm (7-day cycle) +1. **Day 1 — Recruit**: run the sellers-recruiter cycle (target N from channels.json). +2. **Day 2-3 — Intake + test**: every new unit through test-operator; grade recorded. +3. **Day 3-4 — Price**: price per pricing-rules.json; confirm spread ≥ 20%. +4. **Day 4-6 — List + close**: publish listings, closer copy, handle replies/offers. +5. **Day 6 — Compliance**: compliance checker pass on all signed agreements + warranties. +6. **Day 7 — Review**: cycle report — recruited, tested, sold, payout, violations. + +## 90-day plan +- **Days 1-30 (prove intake)**: recruit ≥ 10 signed sellers; ≥ 20 units tested; + ≥ 5 sold; price guide calibrated from real sales, not guesses. +- **Days 31-60 (prove margin)**: ≥ 25% avg spread; price-drop ladder used ≥ 3 times; + zero compliance violations; payout turnaround ≤ 7 days. +- **Days 61-90 (scale)**: second channel live; repeat-seller rate ≥ 30%; unit volume + up 2x with same per-unit cost. + +## Rules +- A unit never lists without: test record, grade, price, compliance-cleared warranty. +- No firm resale-price promises to sellers, ever. +- Every payout logged with the sale that funds it. +- If any agent gate blocks, the pipeline holds — never bypass a gate. diff --git a/packages/hitridge-business-o1/docs/price-guide-starter.md b/packages/hitridge-business-o1/docs/price-guide-starter.md new file mode 100644 index 0000000..4c17fae --- /dev/null +++ b/packages/hitridge-business-o1/docs/price-guide-starter.md @@ -0,0 +1,36 @@ +# Price Guide — Starter Content (O1) + +Seed data for the O1 price engine. Real sales calibrate this in days 1-30 of the +playbook; never treat starter prices as gospel. Currency: BGN. + +## Laptops (working, no major damage) +| Grade | Class | Starter range (BGN) | +|---|---|---| +| A | i5 8th-gen+ / 8GB+ / 256GB SSD | 450-650 | +| B | i3/i5 older gen / 4-8GB / HDD | 250-450 | +| C | needs repair / cosmetic damage | 120-250 | +| reject | no economic path | 0 | + +## Phones (smartphones, working) +| Grade | Class | Starter range (BGN) | +|---|---|---| +| A | flagship ≤ 2 yrs old | 350-600 | +| B | mid-range ≤ 4 yrs | 150-350 | +| C | needs battery/screen work | 60-150 | +| reject | no economic path | 0 | + +## Tablets (working) +| Grade | Class | Starter range (BGN) | +|---|---|---| +| A | current-gen, good battery | 200-400 | +| B | older gen, fine | 100-200 | +| C | needs work | 40-100 | +| reject | no economic path | 0 | + +## Warranty rule +Every listed unit carries a written limited warranty (min 30 days, per agreement +template) covering the tested functions only. Never claim more than tested. + +## Calibration rule +After each sale, log actual sale price vs starter range. Every 5 sales, update the +ranges for that class/grade. Compliance checks price-guide claims before publish. diff --git a/packages/hitridge-business-o1/docs/test-checklist.md b/packages/hitridge-business-o1/docs/test-checklist.md new file mode 100644 index 0000000..8caaabd --- /dev/null +++ b/packages/hitridge-business-o1/docs/test-checklist.md @@ -0,0 +1,40 @@ +# Unit Intake Test — Checklist & Report Template (O1) + +One form per unit. Run in order; never skip. Grade from A/B/C/reject per pricing rules. + +## Unit +- Unit id (Odoo `o1.unit`): ______ +- Type: laptop / phone / tablet / other: ______ +- Make + model: ______ +- Seller ref (Odoo `o1.seller`): ______ +- Intake date: ______ + +## Mechanical / visual +- [ ] Case intact, no cracks/free damage +- [ ] Screen: no dead pixels, no scratches in view area +- [ ] Keyboard / buttons / ports all present and undamaged +- [ ] Battery: holds charge (report measured capacity if available) +- [ ] Photo set taken (front / back / screen-on / serial) + +## Functional +- [ ] Boots to OS, no error loops +- [ ] Display output clean (no lines/artifacts) +- [ ] WiFi / BT work +- [ ] USB / charging port works +- [ ] Storage: SMART / health check OK +- [ ] Speakers / mic / camera work (phone/tablet) + +## Verdict +- Grade: A / B / C / reject +- Pass / fail: ______ +- Repair needed? (only if < 25% of expected resale value): ______ +- Notes (verbatim observations): ______ +- Test record written (Odoo `o1.unit.test`, state `done`): yes / no + +## Report template (cycle summary) +``` +Batch: +Units tested: n | passed: n | rejected: n +Grades: A: n, B: n, C: n +Repairs flagged: n +``` diff --git a/packages/hitridge-business-o1/install.sh b/packages/hitridge-business-o1/install.sh new file mode 100755 index 0000000..e8036d3 --- /dev/null +++ b/packages/hitridge-business-o1/install.sh @@ -0,0 +1,76 @@ +#!/usr/bin/env bash +# O1 target-side verifier + installer (pull model, per manifest-spec v1.0). +# Runs ON THE TARGET. Nothing is deployed unless signature + verify pass. +# +# Usage: +# install.sh --check # verify signature + hash + package integrity only +# install.sh --install # full install (phase 2: enabled on Aura tower) +set -euo pipefail + +PKG_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +MANIFEST="$PKG_ROOT/manifest.json" +PUBKEY="${DEPO_PUBKEY:-/etc/depo/depo-signing.pub}" + +say() { printf '\033[1;32m[install]\033[0m %s\n' "$*"; } +die() { printf '\033[1;31m[install FAIL]\033[0m %s\n' "$*" >&2; exit 1; } + +[ -f "$MANIFEST" ] || die "manifest.json missing at $PKG_ROOT" + +verify_signature() { + [ -f "$PUBKEY" ] || die "signing pubkey not found at $PUBKEY (set DEPO_PUBKEY)" + [ -f "$MANIFEST.sig" ] || die "no signature (.sig) — refusal per spec: No signature = refuse" + [ -f "$MANIFEST.sha256" ] || die "no manifest.sha256 — refusal per spec" + local got want + got="$(sha256sum "$MANIFEST" | cut -d' ' -f1)" + want="$(cat "$MANIFEST.sha256")" + [ "$got" = "$want" ] || die "hash mismatch: manifest was modified after signing" + python3 - "$PUBKEY" "$MANIFEST.sig" "$MANIFEST" <<'PY' || die "ed25519 signature verification failed" +import base64, hashlib, sys +from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PublicKey +pub_path, sig_path, manifest_path = sys.argv[1], sys.argv[2], sys.argv[3] +pub = Ed25519PublicKey.from_public_bytes(open(pub_path, "rb").read()) +sig = base64.b64decode(open(sig_path, "rb").read()) +data = open(manifest_path, "rb").read() +pub.verify(sig, data) +print("ed25519 OK") +PY + say "signature verified" +} + +run_verify() { + verify_signature + python3 - "$MANIFEST" <<'PY' || die "install.verify checklist failed" +import json, os, sys +m = json.load(open(sys.argv[1])) +root = os.path.dirname(os.path.abspath(sys.argv[1])) +missing = [] +for layer, files in m["layers"].items(): + for f in files: + if layer == "odoo_modules": + p = os.path.join(root, "modules", f, "__manifest__.py") + elif layer == "agents": + p = os.path.join(root, "agents", f + ".json") + else: + p = os.path.join(root, f) + if not os.path.exists(p): + missing.append(f) +if missing: + print("MISSING LAYER FILES:", missing) + sys.exit(1) +print("all layer files present:", sum(len(v) for v in m["layers"].values())) +PY + say "verify checklist passed" +} + +case "${1:---check}" in + --check) + run_verify + ;; + --install) + run_verify + die "install is phase 2 (target: Aura tower Odoo). Nothing was deployed." + ;; + *) + die "usage: $0 [--check|--install]" + ;; +esac diff --git a/packages/hitridge-business-o1/manifest.json b/packages/hitridge-business-o1/manifest.json new file mode 100644 index 0000000..14aa68e --- /dev/null +++ b/packages/hitridge-business-o1/manifest.json @@ -0,0 +1,45 @@ +{ + "schema_version": "1.0", + "package_id": "hitridge-business-o1", + "display_name": "O1 — Consignment Refurbished Electronics", + "version": "0.1.0", + "license": "proprietary", + "requires": { + "odoo": ">=18.0", + "agent_runtime": ">=1.0" + }, + "layers": { + "odoo_modules": ["aura_business_core", "hitridge_venture_o1"], + "agents": [ + "o1-sellers-recruiter", + "o1-test-operator", + "o1-closer", + "o1-compliance" + ], + "docs": [ + "docs/playbook.md", + "docs/agreement-template.odt", + "docs/test-checklist.md", + "docs/price-guide-starter.md" + ], + "config": [ + "config/channels.json", + "config/pricing-rules.json" + ], + "brain": [ + "brain/brain.py" + ] + }, + "install": { + "order": ["odoo_modules", "config", "agents", "docs"], + "verify": [ + "all odoo_modules installed", + "all agents registered in runtime", + "venture menu visible", + "no secrets in package (secrets_scan)" + ] + }, + "rollback": { + "strategy": "install previous tag; reverse install order" + } +} diff --git a/packages/hitridge-business-o1/manifest.json.sha256 b/packages/hitridge-business-o1/manifest.json.sha256 new file mode 100644 index 0000000..b543cad --- /dev/null +++ b/packages/hitridge-business-o1/manifest.json.sha256 @@ -0,0 +1 @@ +c0b58b4c1dba1f47f2b41a9fb646f30fdd31fb8fda3cc009f4ebeb4c34b696aa diff --git a/packages/hitridge-business-o1/manifest.json.sig b/packages/hitridge-business-o1/manifest.json.sig new file mode 100644 index 0000000..9a44517 --- /dev/null +++ b/packages/hitridge-business-o1/manifest.json.sig @@ -0,0 +1 @@ +aZN+eg9whiz63/nx9uXNLn9JeSRSeHHcsurL6svPOs80NKU+0B4YRvx5c3tIqUNcHQ/vtiP7wTQe9rlzxyGRDg== \ No newline at end of file diff --git a/packages/hitridge-business-o1/modules/aura_business_core/__init__.py b/packages/hitridge-business-o1/modules/aura_business_core/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/packages/hitridge-business-o1/modules/aura_business_core/__manifest__.py b/packages/hitridge-business-o1/modules/aura_business_core/__manifest__.py new file mode 100644 index 0000000..a493683 --- /dev/null +++ b/packages/hitridge-business-o1/modules/aura_business_core/__manifest__.py @@ -0,0 +1,13 @@ +{ + 'name': 'Aura Business Core', + 'version': '0.1.0', + 'summary': 'Generic venture core: schema, auth, layout for business-in-a-box packages', + 'category': 'Operations', + 'author': 'OrenStack', + 'license': 'OPL-1', + 'depends': ['base'], + 'data': [], + 'installable': True, + 'application': True, + 'auto_install': False, +} diff --git a/packages/hitridge-business-o1/modules/hitridge_venture_o1/__init__.py b/packages/hitridge-business-o1/modules/hitridge_venture_o1/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/packages/hitridge-business-o1/modules/hitridge_venture_o1/__manifest__.py b/packages/hitridge-business-o1/modules/hitridge_venture_o1/__manifest__.py new file mode 100644 index 0000000..ad68d12 --- /dev/null +++ b/packages/hitridge-business-o1/modules/hitridge_venture_o1/__manifest__.py @@ -0,0 +1,16 @@ +{ + 'name': 'Hitridge Venture O1', + 'version': '0.1.0', + 'summary': 'O1 models: seller, unit, order, warranty, price-guide content, pipeline views', + 'category': 'Operations', + 'author': 'OrenStack', + 'license': 'OPL-1', + 'depends': ['base', 'aura_business_core'], + 'data': [ + 'security/ir.model.access.csv', + 'views/venture_menu.xml', + ], + 'installable': True, + 'application': True, + 'auto_install': False, +} diff --git a/packages/hitridge-business-o1/modules/hitridge_venture_o1/models/__init__.py b/packages/hitridge-business-o1/modules/hitridge_venture_o1/models/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/packages/hitridge-business-o1/modules/hitridge_venture_o1/security/ir.model.access.csv b/packages/hitridge-business-o1/modules/hitridge_venture_o1/security/ir.model.access.csv new file mode 100644 index 0000000..dcda487 --- /dev/null +++ b/packages/hitridge-business-o1/modules/hitridge_venture_o1/security/ir.model.access.csv @@ -0,0 +1,4 @@ +id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink +access_o1_seller,o1.seller,model_o1_seller,base.group_user,1,1,1,0 +access_o1_unit,o1.unit,model_o1_unit,base.group_user,1,1,1,0 +access_o1_unit_test,o1.unit.test,model_o1_unit_test,base.group_user,1,1,1,0 diff --git a/packages/hitridge-business-o1/modules/hitridge_venture_o1/views/venture_menu.xml b/packages/hitridge-business-o1/modules/hitridge_venture_o1/views/venture_menu.xml new file mode 100644 index 0000000..d6ae8a0 --- /dev/null +++ b/packages/hitridge-business-o1/modules/hitridge_venture_o1/views/venture_menu.xml @@ -0,0 +1,6 @@ + + + + + + diff --git a/public-keys/depo-signing.pub b/public-keys/depo-signing.pub new file mode 100644 index 0000000..7c63e46 --- /dev/null +++ b/public-keys/depo-signing.pub @@ -0,0 +1 @@ +\~”k"j1QxMdi-N] \ No newline at end of file diff --git a/tools/depo-sign.sh b/tools/depo-sign.sh new file mode 100755 index 0000000..143edef --- /dev/null +++ b/tools/depo-sign.sh @@ -0,0 +1,54 @@ +#!/usr/bin/env bash +# depo-sign.sh — sign a package manifest with an ed25519 key (per manifest-spec v1.0). +# Writes, next to the manifest: +# .sha256 — hex hash of manifest.json +# .sig — base64 ed25519 signature over the manifest bytes +# +# Usage: +# depo-sign.sh [--key ] [--out ] +# --key ed25519 private key (PEM). Default: $DEPO_SIGNING_KEY +# --out where artifacts land. Default: package dir (side-by-side with manifest) +set -euo pipefail + +usage() { echo "usage: $0 [--key ] [--out ]"; exit 1; } + +PKG_DIR="${1:?missing package dir}"; shift +KEY="${DEPO_SIGNING_KEY:-}" +OUT="" +while [ $# -gt 0 ]; do + case "$1" in + --key) KEY="${2:?}"; shift 2 ;; + --out) OUT="${2:?}"; shift 2 ;; + *) usage ;; + esac +done + +MANIFEST="$PKG_DIR/manifest.json" +[ -f "$MANIFEST" ] || { echo "depo-sign: no manifest.json in $PKG_DIR" >&2; exit 1; } +[ -n "$KEY" ] && [ -f "$KEY" ] || { echo "depo-sign: no ed25519 key (set --key or DEPO_SIGNING_KEY)" >&2; exit 1; } +OUT="${OUT:-$PKG_DIR}" +mkdir -p "$OUT" + +SHA="$(sha256sum "$MANIFEST" | cut -d' ' -f1)" +printf '%s\n' "$SHA" > "$OUT/manifest.json.sha256" + +python3 - "$KEY" "$MANIFEST" "$OUT/manifest.json.sig" <<'PY' +import base64, sys +from cryptography.hazmat.primitives import serialization +from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey +key_path, manifest_path, out_path = sys.argv[1], sys.argv[2], sys.argv[3] +data = open(key_path, "rb").read() +try: + key = serialization.load_pem_private_key(data, password=None) +except ValueError: + key = Ed25519PrivateKey.from_private_bytes(data) # raw 32-byte fallback +if not isinstance(key, Ed25519PrivateKey): + raise SystemExit("depo-sign: key is not an ed25519 key") +manifest = open(manifest_path, "rb").read() +sig = key.sign(manifest) +open(out_path, "wb").write(base64.b64encode(sig)) +PY + +echo "depo-sign: signed $MANIFEST" +echo " sha256 $OUT/manifest.json.sha256" +echo " sig $OUT/manifest.json.sig" diff --git a/tools/tool-registry.json b/tools/tool-registry.json new file mode 100644 index 0000000..24c32aa --- /dev/null +++ b/tools/tool-registry.json @@ -0,0 +1,23 @@ +{ + "schema_version": "1.0", + "description": "Reference tool registry for depo packages. validate-depo.sh refuses agent cards that reference tools absent from this registry (principle of least privilege — targets grant exactly this surface).", + "tools": { + "odoo.search_records": "search + read fields (read)", + "odoo.read_record": "read one record by id (read)", + "odoo.read_records": "read multiple records (read)", + "odoo.create_record": "create one record (write)", + "odoo.write_record": "write values to one record (write)", + "odoo.call_method": "call an arbitrary method on an Odoo model (runtime-gated)", + "odoo.launch_campaign": "create + launch a hitridge.campaign (irreversible — campaign gate)", + "file.read": "read a file from the package or target workspace", + "file.write": "write a file in the package or target workspace", + "file.overwrite": "overwrite an existing package file", + "script.run": "run a package script — REQUIRES explicit per-package allowlist; refused by default", + "memory.store": "brain.py store (namespace-scoped)", + "memory.search": "brain.py search (namespace-scoped)", + "memory.clear": "brain.py clear (namespace-scoped)" + }, + "runtime_grants": { + "note": "targets grant ONLY tools listed in each agent card, drawn from this registry. script.run is never granted implicitly." + } +} diff --git a/tools/validate-depo.sh b/tools/validate-depo.sh new file mode 100755 index 0000000..9045e27 --- /dev/null +++ b/tools/validate-depo.sh @@ -0,0 +1,166 @@ +#!/usr/bin/env bash +# validate-depo.sh — validate a package against manifest-spec v1.0 + agent-card-spec v1.0. +# Rejects: malformed manifest, missing layers/files, unregistered tools, missing +# verification, secrets, missing signature/hash. +# +# Usage: +# validate-depo.sh [--key ] [--registry ] +# Writes /VALIDATE on success (the spec-mandated gate file). +set -euo pipefail + +if [ $# -lt 1 ]; then + echo "usage: validate-depo.sh [--key ] [--registry ]" >&2 + exit 1 +fi +PKG_DIR="$1" +shift +PUBKEY="" +REGISTRY="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/tool-registry.json" +while [ $# -gt 0 ]; do + case "$1" in + --key) PUBKEY="${2:?}"; shift 2 ;; + --registry) REGISTRY="${2:?}"; shift 2 ;; + *) echo "validate-depo: unknown arg $1" >&2; exit 1 ;; + esac +done + +MANIFEST="$PKG_DIR/manifest.json" +[ -f "$MANIFEST" ] || { echo "validate-depo: no manifest.json in $PKG_DIR" >&2; exit 1; } + +# Signature + hash gate (refusal per spec: No signature = refuse) +for art in "$MANIFEST.sig" "$MANIFEST.sha256"; do + [ -f "$art" ] || { echo "validate-depo: FAIL — missing $art (refusal per spec)" >&2; exit 1; } +done +GOT="$(sha256sum "$MANIFEST" | cut -d' ' -f1)" +WANT="$(cat "$MANIFEST.sha256")" +[ "$GOT" = "$WANT" ] || { echo "validate-depo: FAIL — manifest hash mismatch (modified after signing)" >&2; exit 1; } +if [ -n "$PUBKEY" ]; then + python3 - "$PUBKEY" "$MANIFEST.sig" "$MANIFEST" <<'PY' || { echo "validate-depo: FAIL — bad signature" >&2; exit 1; } +import base64, sys +from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PublicKey +pub = Ed25519PublicKey.from_public_bytes(open(sys.argv[1], "rb").read()) +sig = base64.b64decode(open(sys.argv[2], "rb").read()) +data = open(sys.argv[3], "rb").read() +pub.verify(sig, data) +PY + echo "validate-depo: signature OK" +fi + +python3 - "$MANIFEST" "$PKG_DIR" "$REGISTRY" <<'PY' || { echo "validate-depo: FAIL — semantic checks" >&2; exit 1; } +import json, os, re, sys +manifest_path, root, registry_path = sys.argv[1], os.path.abspath(sys.argv[2]), sys.argv[3] +m = json.load(open(manifest_path)) +errs = [] + +def req(obj, key, where): + if key not in obj or obj[key] in (None, "", [], {}): + errs.append(f"{where}: missing/invalid '{key}'") + +req(m, "schema_version", "manifest") +if m.get("schema_version") != "1.0": + errs.append("manifest: schema_version must be '1.0'") +req(m, "package_id", "manifest") +req(m, "display_name", "manifest") +req(m, "version", "manifest") +req(m, "license", "manifest") +req(m, "requires", "manifest") +req(m, "layers", "manifest") +req(m, "install", "manifest") +if not m.get("install", {}).get("order"): + errs.append("manifest.install: missing/invalid 'order'") + +if "agents" not in m.get("layers", {}): + errs.append("manifest.layers: no agents layer (spec requires it)") +for name in ("odoo_modules", "agents", "docs", "config"): + if name not in m.get("layers", {}): + errs.append(f"manifest.layers: missing '{name}'") + +# Every layer file must exist on disk. +# odoo_modules entries are module names -> modules//__manifest__.py +# agents entries are card ids -> agents/.json +# docs/config/brain entries are plain paths relative to the package root +for layer, files in m.get("layers", {}).items(): + for f in (files if isinstance(files, list) else [files]): + if layer == "odoo_modules": + p = os.path.join(root, "modules", f, "__manifest__.py") + label = f"modules/{f}/__manifest__.py" + elif layer == "agents": + p = os.path.join(root, "agents", f"{f}.json") + label = f"agents/{f}.json" + else: + p = os.path.join(root, f) + label = f + if not os.path.exists(p): + errs.append(f"manifest.layers.{layer}: file missing on disk: {label}") + +# Agent cards must satisfy agent-card-spec v1.0 +registry = {} +if os.path.exists(registry_path): + registry = json.load(open(registry_path)).get("tools", {}) +agents_dir = os.path.join(root, "agents") +for aid in m.get("layers", {}).get("agents", []): + card = os.path.join(agents_dir, f"{aid}.json") + if not os.path.exists(card): + errs.append(f"agent card missing: {aid}.json") + continue + try: + c = json.load(open(card)) + except json.JSONDecodeError as e: + errs.append(f"agent card {aid}: invalid JSON ({e})") + continue + req(c, "schema_version", f"agent {aid}") + req(c, "agent_id", f"agent {aid}") + if c.get("agent_id") != aid: + errs.append(f"agent {aid}: agent_id mismatch ({c.get('agent_id')})") + req(c, "name", f"agent {aid}") + req(c, "role", f"agent {aid}") + req(c, "description", f"agent {aid}") + req(c, "tools", f"agent {aid}") + req(c, "gates", f"agent {aid}") + req(c, "verification", f"agent {aid}") + req(c, "memory", f"agent {aid}") + if "definition_of_done" not in c.get("verification", {}) and "check" not in c.get("verification", {}): + errs.append(f"agent {aid}: verification must contain definition_of_done or check") + for t in c.get("tools", []): + if registry and t not in registry: + errs.append(f"agent {aid}: unregistered tool '{t}' (not in tool-registry.json)") + if t == "script.run": + errs.append(f"agent {aid}: script.run tool requires explicit allowlist — use the file.* + odoo.* primitives") + for g in c.get("gates", []): + if g not in ("hours", "irreversible-action", "none"): + errs.append(f"agent {aid}: unknown gate '{g}'") + +# No secrets (lenient scan — real scan hooks into the target's secrets_scan) +BANNED = re.compile(r"(api[_-]?key|secret|password|token)\s*[:=]\s*['\"][A-Za-z0-9_\-]{8,}['\"]", re.I) +for dirpath, _, files in os.walk(root): + for fn in files: + if fn.endswith((".sig", ".sha256")): + continue + try: + text = open(os.path.join(dirpath, fn), "rb").read().decode("utf-8", "ignore") + except Exception: + continue + for mt in BANNED.finditer(text): + errs.append(f"possible secret in {os.path.relpath(os.path.join(dirpath, fn), root)}: {mt.group(0)[:40]}") + +if errs: + for e in errs: + print(" -", e, file=sys.stderr) + sys.exit(1) +print("manifest + agent cards + files + tools + secrets scan: PASS") +PY + +# Write the spec-mandated VALIDATE gate file +cat > "$PKG_DIR/VALIDATE" <