feat: ship hitridge-business-o1 v0.1.0 — signed depo package + toolchain

- package: manifest v1.0 (schema, package_id, layers: odoo_modules/agents/docs/config/brain)
- agents: 4 neutral cards + prompts (sellers-recruiter, test-operator, closer, compliance)
  no script.run in v0.1.0; odoo.* + file.* primitives only
- modules: aura_business_core + hitridge_venture_o1 stubs (menus, ACLs, models)
- docs: playbook, test-checklist, price-guide-starter, agreement-template.odt
- config: channels (olx-bg/forums-local), pricing-rules (BGN, A/B/C/reject, drop ladder)
- brain: empty-seed capability contract + seeds README (memories never ship)
- toolchain: depo-sign.sh (ed25519, PEM+raw), validate-depo.sh (schema/files/cards/tools/
  secrets/sig gates -> writes VALIDATE), tool-registry.json
- install.sh: target-side verify (hash + ed25519 + layer files); install is phase 2
- public-keys/depo-signing.pub committed (private key stays out of repo)
- VALIDATE: PASS (signed + verified, tamper-rejection tested)
This commit is contained in:
AG
2026-08-03 21:00:07 +03:00
parent e2f76e4027
commit cb5e5dacef
32 changed files with 918 additions and 0 deletions
+10
View File
@@ -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
@@ -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"
}
}
@@ -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"
}
}
@@ -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"
}
}
@@ -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"
}
}
@@ -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.
@@ -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.
@@ -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.
@@ -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.
@@ -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 <namespace> <key> <value>
brain.py search <namespace> <query> [topk]
brain.py clear <namespace>
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]} <store|search|clear> <namespace> [...]")
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))
@@ -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
@@ -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
}
}
@@ -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"
]
}
}
@@ -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.
@@ -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.
@@ -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: <date>
Units tested: n | passed: n | rejected: n
Grades: A: n, B: n, C: n
Repairs flagged: n
```
+76
View File
@@ -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
@@ -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"
}
}
@@ -0,0 +1 @@
c0b58b4c1dba1f47f2b41a9fb646f30fdd31fb8fda3cc009f4ebeb4c34b696aa
@@ -0,0 +1 @@
aZN+eg9whiz63/nx9uXNLn9JeSRSeHHcsurL6svPOs80NKU+0B4YRvx5c3tIqUNcHQ/vtiP7wTQe9rlzxyGRDg==
@@ -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,
}
@@ -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,
}
@@ -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
1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
2 access_o1_seller o1.seller model_o1_seller base.group_user 1 1 1 0
3 access_o1_unit o1.unit model_o1_unit base.group_user 1 1 1 0
4 access_o1_unit_test o1.unit.test model_o1_unit_test base.group_user 1 1 1 0
@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<menuitem id="menu_o1_root" name="O1 Venture" sequence="10" web_icon="o1"/>
<menuitem id="menu_o1_sellers" name="Sellers" parent="menu_o1_root" action="action_o1_sellers" sequence="10"/>
<menuitem id="menu_o1_units" name="Units" parent="menu_o1_root" action="action_o1_units" sequence="20"/>
</odoo>
+1
View File
@@ -0,0 +1 @@
€‚\~á”kþ"íj1QxMùdi-N½Ì]Ý —÷ì
+54
View File
@@ -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:
# <manifest>.sha256 — hex hash of manifest.json
# <manifest>.sig — base64 ed25519 signature over the manifest bytes
#
# Usage:
# depo-sign.sh <package_dir> [--key <path>] [--out <dir>]
# --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 <package_dir> [--key <path>] [--out <dir>]"; 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"
+23
View File
@@ -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."
}
}
+166
View File
@@ -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 <package_dir> [--key <pubkey>] [--registry <path>]
# Writes <package_dir>/VALIDATE on success (the spec-mandated gate file).
set -euo pipefail
if [ $# -lt 1 ]; then
echo "usage: validate-depo.sh <package_dir> [--key <pubkey>] [--registry <path>]" >&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/<name>/__manifest__.py
# agents entries are card ids -> agents/<id>.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" <<EOF
# VALIDATE — $MANIFEST
Status: PASS
Checked: $(date -u +%Y-%m-%dT%H:%M:%SZ)
- 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
$( [ -n "$PUBKEY" ] && echo "- ed25519 signature verified: OK")
EOF
echo "validate-depo: PASS — VALIDATE written to $PKG_DIR/VALIDATE"