{
  "tool": "api_permissions",
  "args": {
    "sources": [
      {
        "fileName": "support-access-after.bosatsu",
        "source": "package Yichus/Examples/SupportAccess\n\n# A customer support case workspace. Customer handlers prove ownership with\n# owns_case; staff handlers rely on every reaching route requiring support.\nfrom Yichus/IO import IO, flat_map, pure\nfrom Yichus/Access import Principal, AccessRule, AccessSpec, GuardedOrRoles\nfrom Yichus/Data import Db, Table, table, db_read_opt, db_write\nfrom Yichus/Service import (\n  Response, ServiceDef, ReadPerm, WritePerm, handler, route, role_route,\n  service_def\n)\n\nexport (\n  support_api,\n  access_rules,\n  Case(),\n  CaseRequest(),\n  ReplyRequest(),\n  ResolveRequest(),\n  owns_case,\n  view_case,\n  reply_to_case,\n  review_case,\n  resolve_case,\n)\nexposes (Yichus/Access, Yichus/Data, Yichus/IO, Yichus/Service)\n\nstruct Case(\n  id: String,\n  customer_id: String,\n  subject: String,\n  status: String,\n  customer_reply: String,\n  staff_note: String\n)\n\nstruct CaseRequest(case_id: String)\nstruct ReplyRequest(case_id: String, reply: String)\nstruct ResolveRequest(case_id: String, resolution_note: String)\n\ndef cases_table(db: Db) -> Table[Case]:\n  table(db, \"support_cases\")\n\ndef owns_case(user_id: String, support_case: Case) -> Bool:\n  Case(_, customer_id, _, _, _, _) = support_case\n  eq_String(user_id, customer_id)\n\ndef public_case(support_case: Case) -> String:\n  Case(id, _, subject, status, customer_reply, _) = support_case\n  concat_String([id, \" | \", subject, \" | \", status, \" | reply: \", customer_reply])\n\ndef staff_case(support_case: Case) -> String:\n  Case(id, customer_id, subject, status, customer_reply, staff_note) = support_case\n  concat_String([\n    id, \" | customer: \", customer_id, \" | \", subject, \" | \", status,\n    \" | reply: \", customer_reply, \" | private: \", staff_note,\n  ])\n\nmissing_case = Response(404, \"case not found\")\n\nforbidden_case = Response(403, \"this case belongs to another customer\")\n\n# Customer path: a row is used only inside the declared ownership guard.\ndef view_case(db: Db, p: Principal, request: CaseRequest) -> IO[Response]:\n  Principal(user_id, _) = p\n  CaseRequest(case_id) = request\n  found <- flat_map(db_read_opt(cases_table(db), case_id))\n  match found:\n    case None: pure(missing_case)\n    case Some(support_case):\n      if owns_case(user_id, support_case):\n        pure(Response(200, public_case(support_case)))\n      else: pure(forbidden_case)\n\ndef reply_to_case(db: Db, p: Principal, request: ReplyRequest) -> IO[Response]:\n  Principal(user_id, _) = p\n  ReplyRequest(case_id, reply) = request\n  found <- flat_map(db_read_opt(cases_table(db), case_id))\n  match found:\n    case None: pure(missing_case)\n    case Some(support_case):\n      if owns_case(user_id, support_case):\n        Case(id, customer_id, subject, status, _, staff_note) = support_case\n        updated <- flat_map(db_write(cases_table(db), case_id,\n          Case(id, customer_id, subject, status, reply, staff_note)))\n        pure(Response(200, public_case(updated)))\n      else: pure(forbidden_case)\n\n# Staff path: this helper deliberately has no ownership guard. The resource\n# policy permits it only when every route reaching it requires support.\ndef load_for_staff(db: Db, case_id: String) -> IO[Response]:\n  found <- flat_map(db_read_opt(cases_table(db), case_id))\n  match found:\n    case None: pure(missing_case)\n    case Some(support_case): pure(Response(200, staff_case(support_case)))\n\ndef review_case(db: Db, request: CaseRequest) -> IO[Response]:\n  CaseRequest(case_id) = request\n  load_for_staff(db, case_id)\n\ndef resolve_case(db: Db, request: ResolveRequest) -> IO[Response]:\n  ResolveRequest(case_id, resolution_note) = request\n  found <- flat_map(db_read_opt(cases_table(db), case_id))\n  match found:\n    case None: pure(missing_case)\n    case Some(Case(id, customer_id, subject, _, customer_reply, _)):\n      updated <- flat_map(db_write(cases_table(db), case_id,\n        Case(id, customer_id, subject, \"resolved\", customer_reply, resolution_note)))\n      pure(Response(200, staff_case(updated)))\n\naccess_rules = AccessSpec([\n  AccessRule(\"support_cases\", GuardedOrRoles([\"owns_case\"], [\"support\"])),\n])\n\nsupport_api: ServiceDef = service_def(\"support-workspace\", [\n  route(\"/cases/view\", handler(\"view_case\", view_case), [ReadPerm(\"support_cases\")]),\n  route(\"/cases/reply\", handler(\"reply_to_case\", reply_to_case),\n    [ReadPerm(\"support_cases\"), WritePerm(\"support_cases\")]),\n  role_route(\"/staff/review\", handler(\"review_case\", review_case), [\"support\"],\n    [ReadPerm(\"support_cases\")]),\n  role_route(\"/staff/resolve\", handler(\"resolve_case\", resolve_case), [\"support\"],\n    [ReadPerm(\"support_cases\"), WritePerm(\"support_cases\")]),\n  role_route(\"/staff/quick-review\", handler(\"review_case\", review_case), [\"support\"],\n    [ReadPerm(\"support_cases\")]),\n])\n"
      }
    ]
  }
}
